VendorYaml.java
package com.jsql.model.injection.vendor.model;
import com.jsql.model.InjectionModel;
import com.jsql.model.bean.database.Database;
import com.jsql.model.bean.database.Table;
import com.jsql.model.injection.strategy.blind.AbstractInjectionBoolean.BooleanMode;
import com.jsql.model.injection.vendor.model.yaml.Method;
import com.jsql.model.injection.vendor.model.yaml.ModelYaml;
import com.jsql.util.LogLevelUtil;
import com.jsql.util.StringUtil;
import org.apache.commons.codec.binary.Hex;
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 org.yaml.snakeyaml.Yaml;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import static com.jsql.model.accessible.DataAccess.*;
public class VendorYaml implements AbstractVendor {
/**
* Log4j logger sent to view.
*/
private static final Logger LOGGER = LogManager.getRootLogger();
/**
* SQL characters marking the end of the result of an injection.
* Process stops when this schema is encountered:
* <pre>SqLix01x03x03x07
*/
public static final String LEAD_HEX = "0x53714c69";
public static final String LEAD_PIPE = "Sq'||'Li";
public static final String TRAIL_SQL = "%01%03%03%07";
public static final String TRAIL_HEX = "0x01030307";
/**
* SQL character used between each table cells.
* Expected schema of multiple table cells :
* <pre>
* %04[table cell]%05[number of occurrences]%04%06%04[table cell]%05[number of occurrences]%04
*/
public static final String SEPARATOR_CELL_SQL = "%06";
public static final String SEPARATOR_CELL_HEX = "0x06";
public static final String ENCLOSE_VALUE_HEX = "0x04";
/**
* SQL character used between the table cell and the number of occurrence of the cell text.
* Expected schema of a table cell data is
* <pre>%04[table cell]%05[number of occurrences]%04
*/
public static final String SEPARATOR_QTE_SQL = "%05";
public static final String SEPARATOR_QTE_HEX = "0x05";
/**
* SQL character enclosing a table cell returned by injection.
* It allows to detect the correct end of a table cell data during parsing.
* Expected schema of a table cell data is
* <pre>%04[table cell]%05[number of occurrences]%04
*/
public static final String ENCLOSE_VALUE_SQL = "%04";
public static final String CALIBRATOR_SQL = "%23";
public static final String CALIBRATOR_HEX = "0x23";
public static final String FORMAT_INDEX = "1337%s7331";
private static final String BOOLEAN_MODE = "${boolean.mode}";
public static final String LIMIT = "${limit}";
private static final String LIMIT_VALUE = "${limit.value}";
private static final String RESULT_RANGE = "${result_range}";
private static final String INDICE_UNIQUE = "${indice_unique}";
private static final String CALIBRATOR = "${calibrator}";
private static final String INDICES = "${indices}";
public static final String INDICE = "${indice}";
public static final String WINDOW_CHAR = "${window.char}";
public static final String BLOCK_MULTIBIT = "${multibit.block}";
public static final String WINDOW = "${window}";
public static final String CAPACITY = "${capacity}";
public static final String DEFAULT_CAPACITY = "65565";
private static final String SLEEP_TIME = "${sleep_time}";
private static final String BIT = "${bit}";
public static final String INJECTION = "${injection}";
public static final String TEST = "${test}";
private static final String FILEPATH = "${filepath}";
private static final String FILEPATH_HEX = "${filepath.hex}";
private static final String BODY_HEX = "${body.hex}";
private static final String FIELDS = "${fields}";
private static final String FIELD = "${field.value}";
private static final String TABLE = "${table}";
private static final String DATABASE = "${database}";
private static final String TABLE_HEX = "${table.hex}";
private static final String DATABASE_HEX = "${database.hex}";
private final ModelYaml modelYaml;
private final InjectionModel injectionModel;
public VendorYaml(String fileYaml, InjectionModel injectionModel) {
this.injectionModel = injectionModel;
var yaml = new Yaml();
this.modelYaml = yaml.loadAs(
VendorYaml.class.getClassLoader().getResourceAsStream("vendor/"+ fileYaml),
ModelYaml.class
);
}
@Override
public String sqlDatabases() {
String sqlQuery = this.modelYaml.getResource().getSchema().getDatabase();
if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isDiosStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getDios().getDatabase())) {
sqlQuery = this.modelYaml.getResource().getDios().getDatabase();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Dios] activated but database query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
} else if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isZipStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getZip().getDatabase())) {
sqlQuery = this.modelYaml.getResource().getZip().getDatabase();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Zip] activated but database query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
}
return sqlQuery;
}
@Override
public String sqlTables(Database database) {
String sqlQuery = this.modelYaml.getResource().getSchema().getTable();
if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isDiosStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getDios().getTable())) {
sqlQuery = this.modelYaml.getResource().getDios().getTable();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Dios] activated but table query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
} else if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isZipStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getZip().getTable())) {
sqlQuery = this.modelYaml.getResource().getZip().getTable();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Zip] activated but table query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
}
String databaseUtf8 = Hex.encodeHexString(database.toString().getBytes(StandardCharsets.UTF_8));
return sqlQuery
.replace(DATABASE_HEX, databaseUtf8)
.replace(DATABASE, database.toString());
}
@Override
public String sqlColumns(Table table) {
String sqlQuery = this.modelYaml.getResource().getSchema().getColumn();
if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isDiosStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getDios().getColumn())) {
sqlQuery = this.modelYaml.getResource().getDios().getColumn();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Dios] activated but column query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
} else if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isZipStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getZip().getColumn())) {
sqlQuery = this.modelYaml.getResource().getZip().getColumn();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Zip] activated but column query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
}
String databaseUtf8 = Hex.encodeHexString(table.getParent().toString().getBytes(StandardCharsets.UTF_8));
String tableUtf8 = Hex.encodeHexString(table.toString().getBytes(StandardCharsets.UTF_8));
return sqlQuery
.replace(DATABASE_HEX, databaseUtf8)
.replace(TABLE_HEX, tableUtf8)
.replace(DATABASE, table.getParent().toString())
.replace(TABLE, table.toString());
}
@Override
public String sqlRows(String[] namesColumns, Database database, Table table) {
String sqlField = this.modelYaml.getResource().getSchema().getRow().getFields().getField();
String sqlConcatFields = this.modelYaml.getResource().getSchema().getRow().getFields().getConcat();
String sqlQuery = this.modelYaml.getResource().getSchema().getRow().getQuery();
if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isDiosStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getDios().getDatabase())) {
sqlField = this.modelYaml.getResource().getDios().getRow().getFields().getField();
sqlConcatFields = this.modelYaml.getResource().getDios().getRow().getFields().getConcat();
sqlQuery = this.modelYaml.getResource().getDios().getRow().getQuery();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Dios] activated but row query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
} else if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isZipStrategy()) {
if (StringUtils.isNotBlank(this.modelYaml.getResource().getZip().getDatabase())) {
sqlField = this.modelYaml.getResource().getZip().getRow().getFields().getField();
sqlConcatFields = this.modelYaml.getResource().getZip().getRow().getFields().getConcat();
sqlQuery = this.modelYaml.getResource().getZip().getRow().getQuery();
} else {
LOGGER.log(
LogLevelUtil.CONSOLE_INFORM,
"Strategy [Zip] activated but row query is undefined for [{}], fallback to default",
() -> this.injectionModel.getMediatorVendor().getVendor()
);
}
}
var matcherSqlField = Pattern.compile("(?s)(.*)"+ Pattern.quote(FIELD) +"(.*)").matcher(sqlField);
String leadSqlField = StringUtils.EMPTY;
String trailSqlField = StringUtils.EMPTY;
if (matcherSqlField.find()) {
leadSqlField = matcherSqlField.group(1);
trailSqlField = matcherSqlField.group(2);
}
var namesColumnUtf8 = new String[namesColumns.length];
for (var i = 0 ; i < namesColumns.length ; i++) {
namesColumnUtf8[i] = StringUtil.detectUtf8(namesColumns[i]);
namesColumnUtf8[i] = URLEncoder.encode(namesColumnUtf8[i], StandardCharsets.UTF_8);
}
var nameDatabaseUtf8 = StringUtil.detectUtf8(database.toString());
nameDatabaseUtf8 = URLEncoder.encode(nameDatabaseUtf8, StandardCharsets.UTF_8);
var nameTableUtf8 = StringUtil.detectUtf8(table.toString());
nameTableUtf8 = URLEncoder.encode(nameTableUtf8, StandardCharsets.UTF_8);
return sqlQuery.replace(
FIELDS,
leadSqlField
+ String.join(
trailSqlField + sqlConcatFields + leadSqlField,
namesColumnUtf8
)
+ trailSqlField
)
.replace(DATABASE, nameDatabaseUtf8)
.replace(TABLE, nameTableUtf8);
}
@Override
public String sqlFileRead(String filePath) {
return this.modelYaml.getResource().getFile().getRead()
.replace(FILEPATH_HEX, Hex.encodeHexString(filePath.getBytes(StandardCharsets.UTF_8))) // MySQL
.replace(FILEPATH, filePath); // PostgreSQL
}
@Override
public String sqlTextIntoFile(String body, String path) {
String visibleIndex = String.format(
VendorYaml.FORMAT_INDEX,
this.injectionModel.getMediatorStrategy().getSpecificNormal().getVisibleIndex()
);
return this.injectionModel.getIndexesInUrl()
.replaceAll(
visibleIndex,
this.modelYaml.getResource().getFile().getWrite()
.getBody()
.replace(
BODY_HEX,
Hex.encodeHexString(body.getBytes(StandardCharsets.UTF_8))
)
)
+ StringUtils.SPACE
+ this.modelYaml.getResource().getFile().getWrite()
.getPath()
.replace(FILEPATH, path);
}
@Override
public String sqlTestBlind(String check, BooleanMode blindMode) {
String replacement = getMode(blindMode);
return this.modelYaml.getStrategy().getBoolean()
.getBlind()
.replace(BOOLEAN_MODE, replacement)
.replace(TEST, check)
.trim(); // trim spaces in '${boolean.mode} ${test}' when no mode, not covered by cleanSql()
}
@Override
public String sqlBitTestBlind(String inj, int indexCharacter, int bit, BooleanMode blindMode) {
String replacement = getMode(blindMode);
return this.modelYaml.getStrategy().getBoolean()
.getBlind()
.replace(BOOLEAN_MODE, replacement)
.replace(
TEST,
this.modelYaml.getStrategy().getBoolean().getTest().getBit()
.replace(INJECTION, inj)
.replace(WINDOW_CHAR, Integer.toString(indexCharacter))
.replace(BIT, Integer.toString(bit))
)
.trim(); // trim spaces in '${boolean.mode} ${test}' when no mode, not covered by cleanSql()
}
@Override
public String sqlTimeTest(String check, BooleanMode blindMode) {
String replacement = getMode(blindMode);
int countSleepTimeStrategy = this.injectionModel.getMediatorUtils().getPreferencesUtil().isLimitingSleepTimeStrategy()
? this.injectionModel.getMediatorUtils().getPreferencesUtil().countSleepTimeStrategy()
: 5;
return this.modelYaml.getStrategy().getBoolean()
.getTime()
.replace(BOOLEAN_MODE, replacement)
.replace(TEST, check)
.replace(SLEEP_TIME, Long.toString(countSleepTimeStrategy))
.trim(); // trim spaces in '${boolean.mode} ${test}' when no mode, not covered by cleanSql()
}
@Override
public String sqlBitTestTime(String inj, int indexCharacter, int bit, BooleanMode blindMode) {
String replacement = getMode(blindMode);
int countSleepTimeStrategy = this.injectionModel.getMediatorUtils().getPreferencesUtil().isLimitingSleepTimeStrategy()
? this.injectionModel.getMediatorUtils().getPreferencesUtil().countSleepTimeStrategy()
: 5;
return this.modelYaml.getStrategy().getBoolean()
.getTime()
.replace(BOOLEAN_MODE, replacement)
.replace(
TEST,
this.modelYaml.getStrategy().getBoolean().getTest()
.getBit()
.replace(INJECTION, inj)
.replace(WINDOW_CHAR, Integer.toString(indexCharacter))
.replace(BIT, Integer.toString(bit))
)
.replace(SLEEP_TIME, Long.toString(countSleepTimeStrategy))
.trim(); // trim spaces in '${boolean.mode} ${test}' when no mode, not covered by cleanSql()
}
private String getMode(BooleanMode blindMode) {
String replacement;
switch (blindMode) {
case AND: replacement = this.modelYaml.getStrategy().getBoolean().getModeAnd(); break;
case OR: replacement = this.modelYaml.getStrategy().getBoolean().getModeOr(); break;
case STACKED: replacement = this.modelYaml.getStrategy().getBoolean().getModeStacked(); break;
case NO_MODE:
default: replacement = StringUtils.EMPTY; break;
}
return replacement;
}
@Override
public String sqlBlind(String sqlQuery, String startPosition, boolean isReport) {
return VendorYaml.replaceTags(
getSlidingWindow(isReport)
.replace(INJECTION, sqlQuery)
.replace(WINDOW_CHAR, startPosition)
.replace(CAPACITY, DEFAULT_CAPACITY)
);
}
@Override
public String sqlTime(String sqlQuery, String startPosition, boolean isReport) {
return VendorYaml.replaceTags(
getSlidingWindow(isReport)
.replace(INJECTION, sqlQuery)
.replace(WINDOW_CHAR, startPosition)
.replace(CAPACITY, DEFAULT_CAPACITY)
);
}
@Override
public String sqlMultibit(String inj, int indexCharacter, int block){
return this.modelYaml.getStrategy().getBoolean().getMultibit()
.replace(INJECTION, inj)
.replace(WINDOW_CHAR, Integer.toString(indexCharacter))
.replace(BLOCK_MULTIBIT, Integer.toString(block));
}
@Override
public String sqlErrorCalibrator(Method errorMethod) {
return VendorYaml.replaceTags(
errorMethod.getQuery()
.replace(VendorYaml.WINDOW, this.modelYaml.getStrategy().getConfiguration().getSlidingWindow())
.replace(VendorYaml.INJECTION, this.modelYaml.getStrategy().getConfiguration().getCalibrator())
.replace(VendorYaml.WINDOW_CHAR, "1")
.replace(VendorYaml.CAPACITY, Integer.toString(errorMethod.getCapacity()))
);
}
@Override
public String sqlErrorIndice(Method errorMethod) {
var indexZeroToFind = "0";
return VendorYaml.replaceTags(
errorMethod.getQuery()
.replace(VendorYaml.WINDOW, this.modelYaml.getStrategy().getConfiguration().getSlidingWindow())
.replace(VendorYaml.INJECTION, this.modelYaml.getStrategy().getConfiguration().getFailsafe().replace(INDICE, indexZeroToFind))
.replace(VendorYaml.WINDOW_CHAR, "1")
.replace(VendorYaml.CAPACITY, Integer.toString(errorMethod.getCapacity()))
);
}
@Override
public String sqlError(String sqlQuery, String startPosition, int indexMethodError, boolean isReport) {
return VendorYaml.replaceTags(
this.modelYaml.getStrategy().getError().getMethod().get(indexMethodError).getQuery()
.replace(WINDOW, getSlidingWindow(isReport))
.replace(INJECTION, sqlQuery)
.replace(WINDOW_CHAR, startPosition)
.replace(
CAPACITY,
Integer.toString(
this.modelYaml.getStrategy().getError()
.getMethod()
.get(indexMethodError)
.getCapacity()
)
)
);
}
@Override
public String sqlNormal(String sqlQuery, String startPosition, boolean isReport) {
return VendorYaml.replaceTags(
getSlidingWindow(isReport)
.replace(INJECTION, sqlQuery)
.replace(WINDOW_CHAR, startPosition)
.replace(CAPACITY, this.injectionModel.getMediatorStrategy().getNormal().getPerformanceLength())
);
}
@Override
public String sqlStacked(String sqlQuery, String startPosition, boolean isReport) {
return this.modelYaml.getStrategy().getStacked().replace(
WINDOW,
VendorYaml.replaceTags(
getSlidingWindow(isReport)
.replace(INJECTION, sqlQuery)
.replace(WINDOW_CHAR, startPosition)
.replace(CAPACITY, DEFAULT_CAPACITY)
)
);
}
@Override
public String sqlCapacity(String[] indexes) {
String regexIndexes = String.join("|", indexes);
String regexVisibleIndexesToFind = String.format(VendorYaml.FORMAT_INDEX, "(%s)");
return this.injectionModel.getIndexesInUrl().replaceAll(
String.format(regexVisibleIndexesToFind, regexIndexes),
VendorYaml.replaceTags(
this.modelYaml.getStrategy().getNormal().getCapacity()
.replace(CALIBRATOR, this.modelYaml.getStrategy().getConfiguration().getCalibrator())
.replace(INDICE, "$1")
)
);
}
@Override
public String sqlIndices(Integer nbFields) {
String replaceTag = StringUtils.EMPTY;
List<String> fields = new ArrayList<>();
var indice = 1;
for ( ; indice <= nbFields ; indice++) {
String field = this.modelYaml.getStrategy().getConfiguration().getFailsafe().replace(INDICE, Integer.toString(indice));
fields.add(field);
replaceTag = field;
}
indice--;
return this.modelYaml.getStrategy().getNormal()
.getIndices()
.replace(
INDICES,
String.join(",", fields.toArray(new String[0]))
)
.replace(INDICE_UNIQUE, replaceTag)
.replace(
RESULT_RANGE,
String.join(",", Collections.nCopies(indice, "r"))
);
}
@Override
public String sqlLimit(Integer limitSQLResult) {
var limitBoundary = 0;
try {
limitBoundary = Integer.parseInt(this.modelYaml.getStrategy().getConfiguration().getLimitBoundary());
} catch (NumberFormatException e) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect Limit start index, force to 0");
}
return this.modelYaml.getStrategy().getConfiguration()
.getLimit()
.replace(LIMIT_VALUE, Integer.toString(limitSQLResult + limitBoundary));
}
@Override
public String fingerprintErrorsAsRegex() {
return "(?si)"+ StringUtils.join(
this.modelYaml.getStrategy().getConfiguration().getFingerprint()
.getErrorMessage()
.stream()
.map(m -> ".*"+ m +".*")
.toArray(),
"|"
);
}
public static String replaceTags(String sqlRequest) {
return sqlRequest
.replace("${enclose_value_sql}", ENCLOSE_VALUE_SQL)
.replace("${enclose_value_hex}", ENCLOSE_VALUE_HEX)
.replace("${separator_qte_sql}", SEPARATOR_QTE_SQL)
.replace("${separator_qte_hex}", SEPARATOR_QTE_HEX)
.replace("${separator_cell_sql}", SEPARATOR_CELL_SQL)
.replace("${separator_cell_hex}", SEPARATOR_CELL_HEX)
.replace("${calibrator_sql}", CALIBRATOR_SQL)
.replace("${calibrator_hex}", CALIBRATOR_HEX)
.replace("${trail_sql}", TRAIL_SQL)
.replace("${trail_hex}", TRAIL_HEX)
.replace("${lead}", LEAD)
.replace("${lead_hex}", LEAD_HEX)
.replace("${lead_pipe}", LEAD_PIPE);
}
/**
* Get payload with sliding window except for vulnerability report
*/
private String getSlidingWindow(boolean isReport) {
return isReport
? "(" + INJECTION + ")"
: this.modelYaml.getStrategy().getConfiguration().getSlidingWindow();
}
// Getter and setter
@Override
public String sqlBooleanBlind() {
return this.modelYaml.getStrategy().getBoolean().getBlind();
}
@Override
public String sqlBooleanTime() {
return this.modelYaml.getStrategy().getBoolean().getTime();
}
@Override
public String sqlInfos() {
return this.modelYaml.getResource().getInfo();
}
@Override
public String sqlPrivilegeTest() {
return this.modelYaml.getResource().getFile().getPrivilege();
}
@Override
public List<String> getFalsy() {
return this.modelYaml.getStrategy().getBoolean().getTest().getFalsy();
}
@Override
public List<String> getTruthy() {
return this.modelYaml.getStrategy().getBoolean().getTest().getTruthy();
}
@Override
public String sqlTestBooleanInitialization() {
return this.modelYaml.getStrategy().getBoolean().getTest().getInitialization();
}
@Override
public String sqlOrderBy() {
return this.modelYaml.getStrategy().getNormal().getOrderBy();
}
@Override
public String endingComment() {
if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isUrlRandomSuffixDisabled()) {
return this.modelYaml.getStrategy().getConfiguration().getEndingComment();
} else {
return this.modelYaml.getStrategy().getConfiguration().getEndingComment()
// Allows Boolean match fingerprinting on host errors
+ RandomStringUtils.randomAlphanumeric(4);
}
}
@Override
public ModelYaml getModelYaml() {
return this.modelYaml;
}
}