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;
12  
13  import com.jsql.model.accessible.DataAccess;
14  import com.jsql.model.accessible.ResourceAccess;
15  import com.jsql.view.subscriber.Seal;
16  import com.jsql.model.exception.JSqlException;
17  import com.jsql.model.exception.JSqlRuntimeException;
18  import com.jsql.model.injection.method.AbstractMethodInjection;
19  import com.jsql.model.injection.method.MediatorMethod;
20  import com.jsql.model.injection.strategy.MediatorStrategy;
21  import com.jsql.model.injection.strategy.blind.callable.AbstractCallableBit;
22  import com.jsql.model.injection.engine.MediatorEngine;
23  import com.jsql.model.injection.engine.model.EngineYaml;
24  import com.jsql.util.*;
25  import com.jsql.util.GitUtil.ShowOnConsole;
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  
30  import javax.swing.*;
31  import java.awt.*;
32  import java.io.IOException;
33  import java.io.Serializable;
34  import java.net.*;
35  import java.net.http.HttpRequest;
36  import java.net.http.HttpRequest.BodyPublishers;
37  import java.net.http.HttpRequest.Builder;
38  import java.net.http.HttpResponse;
39  import java.net.http.HttpResponse.BodyHandlers;
40  import java.nio.charset.StandardCharsets;
41  import java.text.DecimalFormat;
42  import java.time.Duration;
43  import java.util.AbstractMap.SimpleEntry;
44  import java.util.Map;
45  import java.util.regex.Matcher;
46  import java.util.stream.Collectors;
47  import java.util.stream.Stream;
48  
49  /**
50   * Model class of MVC pattern for processing SQL injection automatically.<br>
51   * Different views can be attached to this observable, like Swing or command line, in order to separate
52   * the functional job from the graphical processing.<br>
53   * The Model has a specific database engine and strategy which run an automatic injection to get name of
54   * databases, tables, columns and values, and it can also retrieve resources like files and shell.<br>
55   * Tasks are run in multi-threads in general to speed the process.
56   */
57  public class InjectionModel extends AbstractModelObservable implements Serializable {
58      
59      private static final Logger LOGGER = LogManager.getRootLogger();
60      
61      private final transient MediatorEngine mediatorEngine = new MediatorEngine(this);
62      private final transient MediatorMethod mediatorMethod = new MediatorMethod(this);
63      private final transient DataAccess dataAccess = new DataAccess(this);
64      private final transient ResourceAccess resourceAccess = new ResourceAccess(this);
65      private final transient PropertiesUtil propertiesUtil = new PropertiesUtil();
66      private final transient MediatorUtils mediatorUtils;
67      private final transient MediatorStrategy mediatorStrategy;
68  
69      public static final String STAR = "*";
70      public static final String BR = "<br>&#10;";
71  
72      /**
73       * initialUrl transformed to a correct injection url.
74       */
75      private String analysisReport = StringUtils.EMPTY;
76  
77      /**
78       * Allow to directly start an injection after a failed one
79       * without asking the user 'Start a new injection?'.
80       */
81      private boolean shouldErasePreviousInjection = false;
82      private boolean isScanning = false;  // prevent injecting when already scanning
83      private boolean isInjectingWithoutScan = false;  // prevent scanning when already injecting
84  
85      public InjectionModel() {
86          this.mediatorStrategy = new MediatorStrategy(this);
87          this.mediatorUtils = new MediatorUtils(
88              this.propertiesUtil,
89              new ConnectionUtil(this),
90              new AuthenticationUtil(),
91              new GitUtil(this),
92              new HeaderUtil(this),
93              new ParameterUtil(this),
94              new ExceptionUtil(this),
95              new SoapUtil(this),
96              new MultipartUtil(this),
97              new CookiesUtil(this),
98              new JsonUtil(this),
99              new PreferencesUtil(),
100             new ProxyUtil(),
101             new ThreadUtil(this),
102             new TamperingUtil(),
103             new UserAgentUtil(),
104             new CsrfUtil(this),
105             new DigestUtil(this),
106             new FormUtil(this),
107             new CertificateUtil()
108         );
109     }
110 
111     /**
112      * Reset each injection attributes: Database metadata, General Thread status, Strategy.
113      */
114     public void resetModel() {
115         this.mediatorStrategy.getTime().setApplicable(false);
116         this.mediatorStrategy.getBlindBin().setApplicable(false);
117         this.mediatorStrategy.getBlindBit().setApplicable(false);
118         this.mediatorStrategy.getMultibit().setApplicable(false);
119         this.mediatorStrategy.getDns().setApplicable(false);
120         this.mediatorStrategy.getError().setApplicable(false);
121         this.mediatorStrategy.getStack().setApplicable(false);
122         this.mediatorStrategy.getUnion().setApplicable(false);
123         this.mediatorStrategy.setStrategy(null);
124 
125         this.mediatorStrategy.getSpecificUnion().setVisibleIndex(null);
126         this.mediatorStrategy.getSpecificUnion().setIndexesInUrl(StringUtils.EMPTY);
127 
128         this.analysisReport = StringUtils.EMPTY;
129         this.isStoppedByUser = false;
130         this.shouldErasePreviousInjection = false;
131 
132         this.mediatorUtils.csrfUtil().setTokenCsrf(null);
133         this.mediatorUtils.digestUtil().setTokenDigest(null);
134         this.mediatorUtils.threadUtil().reset();
135     }
136 
137     /**
138      * Prepare the injection process, can be interrupted by the user (via shouldStopAll).
139      * Erase all attributes eventually defined in a previous injection.
140      * Run by Scan, Standard and TU.
141      */
142     public void beginInjection() {
143         this.resetModel();
144         try {
145             if (this.mediatorUtils.proxyUtil().isNotLive(ShowOnConsole.YES)) {
146                 return;
147             }
148             LOGGER.log(
149                 LogLevelUtil.CONSOLE_INFORM,
150                 "{}: {}",
151                 () -> I18nUtil.valueByKey("LOG_START_INJECTION"),
152                 () -> this.mediatorUtils.connectionUtil().getUrlByUser()
153             );
154             
155             // Check general integrity if user's parameters
156             this.mediatorUtils.parameterUtil().checkParametersFormat();
157             this.mediatorUtils.connectionUtil().testConnection();
158 
159             // TODO Check all path params URL segments
160             boolean hasFoundInjection = this.mediatorMethod.getQuery().testParameters(false);
161             hasFoundInjection = this.mediatorUtils.multipartUtil().testParameters(hasFoundInjection);
162             hasFoundInjection = this.mediatorUtils.soapUtil().testParameters(hasFoundInjection);
163             hasFoundInjection = this.mediatorMethod.getRequest().testParameters(hasFoundInjection);
164             hasFoundInjection = this.mediatorMethod.getHeader().testParameters(hasFoundInjection);
165             hasFoundInjection = this.mediatorUtils.cookiesUtil().testParameters(hasFoundInjection);
166 
167             if (hasFoundInjection && !this.isScanning) {
168                 if (!this.getMediatorUtils().preferencesUtil().isNotShowingVulnReport()) {
169                     this.sendToViews(new Seal.CreateAnalysisReport(this.analysisReport));
170                 }
171                 if (this.getMediatorUtils().preferencesUtil().isZipStrategy()) {
172                     LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Using Zip mode for reduced query size");
173                 } else if (this.getMediatorUtils().preferencesUtil().isDiosStrategy()) {
174                     LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Using Dump In One Shot strategy for single query dump");
175                 }
176                 if (!this.mediatorUtils.preferencesUtil().isNotInjectingMetadata()) {
177                     this.dataAccess.getDatabaseInfos();
178                 }
179                 this.dataAccess.listDatabases();
180             }
181             
182             LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, () -> I18nUtil.valueByKey("LOG_DONE"));
183             this.shouldErasePreviousInjection = hasFoundInjection;
184         } catch (InterruptedException e) {
185             LOGGER.log(LogLevelUtil.IGNORE, e, e);
186             Thread.currentThread().interrupt();
187         } catch (JSqlRuntimeException | JSqlException | IOException e) {  // Catch expected exceptions only
188             LOGGER.log(
189                 LogLevelUtil.CONSOLE_ERROR,
190                 "Interruption: {}",
191                 e.getMessage() == null ? InjectionModel.getImplicitReason(e) : e.getMessage()
192             );
193         } finally {
194             this.sendToViews(new Seal.EndPreparation());
195             this.isInjectingWithoutScan = false;
196         }
197     }
198     
199     public static String getImplicitReason(Throwable e) {
200         String message = e.getClass().getSimpleName();
201         if (e.getMessage() != null) {
202             message += ": "+ e.getMessage();
203         }
204         if (e.getCause() != null && !e.equals(e.getCause())) {
205             message += " > "+ InjectionModel.getImplicitReason(e.getCause());
206         }
207         return message;
208     }
209     
210     /**
211      * Run an HTTP connection to the web server.
212      * @param dataInjection SQL query
213      * @return source code of current page
214      */
215     @Override
216     public String inject(
217         String dataInjection,
218         boolean isUsingIndex,
219         String metadataInjectionProcess,
220         AbstractCallableBit<?> callableBoolean,
221         boolean isReport
222     ) {
223         // Temporary url, we go from "select 1,2,3,4..." to "select 1,([complex query]),2...", but keep initial url
224         String urlInjection = this.mediatorUtils.connectionUtil().getUrlBase();
225         urlInjection = this.mediatorStrategy.buildPath(urlInjection, isUsingIndex, dataInjection);
226         urlInjection = StringUtil.cleanSql(urlInjection.trim());
227 
228         URL urlObject;
229         String urlInjectionFixed;
230         try {
231             urlInjectionFixed = this.initQueryString(
232                 isUsingIndex,
233                 urlInjection,
234                 dataInjection
235             );
236             urlObject = new URI(urlInjectionFixed).toURL();
237         } catch (MalformedURLException | URISyntaxException e) {
238             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect Query URL: {}", e.getMessage());
239             return StringUtils.EMPTY;
240         }
241 
242         String pageSource = StringUtils.EMPTY;
243         
244         // Define the connection
245         try {
246             var httpRequestBuilder = HttpRequest.newBuilder()
247                 .uri(URI.create(urlObject.toString()))
248                 .setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, "text/plain")
249                 .timeout(Duration.ofSeconds(15));
250             
251             this.mediatorUtils.csrfUtil().addHeaderToken(httpRequestBuilder);
252             this.mediatorUtils.digestUtil().addHeaderToken(httpRequestBuilder);
253             this.mediatorUtils.connectionUtil().setCustomUserAgent(httpRequestBuilder);
254 
255             String body = this.initRequest(isUsingIndex, dataInjection, httpRequestBuilder);
256             this.initHeader(isUsingIndex, dataInjection, httpRequestBuilder);
257             
258             var httpRequest = httpRequestBuilder.build();
259             if (isReport) {
260                 Color colorReport = UIManager.getColor("TextArea.inactiveForeground");
261                 String report = InjectionModel.BR + StringUtil.formatReport(colorReport, "Method: ") + httpRequest.method();
262                 report += InjectionModel.BR + StringUtil.formatReport(colorReport, "Path: ") + httpRequest.uri().getPath();
263                 if (httpRequest.uri().getQuery() != null) {
264                     report += InjectionModel.BR + StringUtil.formatReport(colorReport, "Query: ") + httpRequest.uri().getQuery();
265                 }
266                 if (
267                     !(this.mediatorUtils.parameterUtil().getListRequest().isEmpty()
268                     && this.mediatorUtils.csrfUtil().getTokenCsrf() == null)
269                 ) {
270                     report += InjectionModel.BR + StringUtil.formatReport(colorReport, "Body: ") + body;
271                 }
272                 report += InjectionModel.BR 
273                     + StringUtil.formatReport(colorReport, "Header: ")
274                     + httpRequest.headers().map().entrySet().stream()
275                     .map(entry -> 
276                         String.format("%s: %s", entry.getKey(), 
277                         String.join(StringUtils.EMPTY, entry.getValue()))
278                     )
279                     .collect(Collectors.joining(InjectionModel.BR));
280                 return report;
281             }
282             
283             HttpResponse<String> response = this.getMediatorUtils().connectionUtil().getHttpClient().build().send(
284                 httpRequestBuilder.build(),
285                 BodyHandlers.ofString()
286             );
287             if (this.mediatorUtils.parameterUtil().isRequestSoap()) {
288                 // Invalid XML control chars like \x04 requires urlencoding from server
289                 pageSource = URLDecoder.decode(response.body(), StandardCharsets.UTF_8);
290                 pageSource = StringUtil.fromHtml(pageSource);
291             } else {
292                 pageSource = response.body();
293             }
294 
295             Map<String, String> headersResponse = ConnectionUtil.getHeadersMap(response);
296             int sizeHeaders = headersResponse.keySet()
297                 .stream()
298                 .map(key -> headersResponse.get(key).length() + key.length())
299                 .mapToInt(Integer::intValue)
300                 .sum();
301             float size = (float) (pageSource.length() + sizeHeaders) / 1024;
302             var decimalFormat = new DecimalFormat("0.000");
303 
304             String pageSourceFixed = pageSource
305                 .replaceAll("("+ EngineYaml.CALIBRATOR_SQL +"){60,}", "$1...")  // Remove ranges of # created by calibration
306                 .replaceAll("(jIyM){60,}", "$1...");  // Remove batch of chars created by Dios
307 
308             // Send data to Views
309             this.sendToViews(new Seal.MessageHeader(
310                 urlInjectionFixed,
311                 body,
312                 ConnectionUtil.getHeadersMap(httpRequest.headers()),
313                 headersResponse,
314                 pageSourceFixed,
315                 decimalFormat.format(size),
316                 this.mediatorStrategy.getMeta(),
317                 metadataInjectionProcess,
318                 callableBoolean
319             ));
320         } catch (IOException e) {
321             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Error during connection: {}", e.getMessage());
322         } catch (InterruptedException e) {
323             LOGGER.log(LogLevelUtil.IGNORE, e, e);
324             Thread.currentThread().interrupt();
325         }
326 
327         return pageSource;
328     }
329 
330     private String initQueryString(boolean isUsingIndex, String urlInjection, String dataInjection) {
331         String urlInjectionFixed = urlInjection;
332         if (
333             this.mediatorUtils.parameterUtil().getListQueryString().isEmpty()
334             && !this.mediatorUtils.preferencesUtil().isProcessingCsrf()
335         ) {
336             return urlInjectionFixed;
337         }
338             
339         // URL without query string like Request and Header can receive
340         // new params from <form> parsing, in that case add the '?' to URL
341         if (!urlInjectionFixed.contains("?")) {
342             urlInjectionFixed += "?";
343         }
344         urlInjectionFixed += this.buildQuery(
345             this.mediatorMethod.getQuery(),
346             this.mediatorUtils.parameterUtil().getQueryStringFromEntries(),
347             isUsingIndex,
348             dataInjection
349         );
350         return this.mediatorUtils.csrfUtil().addQueryStringToken(urlInjectionFixed);
351     }
352 
353     private void initHeader(boolean isUsingIndex, String dataInjection, Builder httpRequest) {
354         if (!this.mediatorUtils.parameterUtil().getListHeader().isEmpty()) {
355             Stream.of(
356                 this.buildQuery(
357                     this.mediatorMethod.getHeader(),
358                     this.mediatorUtils.parameterUtil().getHeaderFromEntries(),
359                     isUsingIndex,
360                     dataInjection
361                 )
362                 .split("\\\\r\\\\n")
363             )
364             .forEach(header -> {
365                 if (header.split(":").length == 2) {
366                     try {  // TODO Should not catch, rethrow or use runtime exception
367                         HeaderUtil.sanitizeHeaders(
368                             httpRequest,
369                             new SimpleEntry<>(
370                                 header.split(":")[0],
371                                 header.split(":")[1]
372                             )
373                         );
374                     } catch (JSqlException e) {
375                         LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Headers sanitizing issue caught already during connection, ignoring", e);
376                     }
377                 }
378             });
379         }
380     }
381 
382     private String initRequest(boolean isUsingIndex, String dataInjection, Builder httpRequest) {
383         if (
384             this.mediatorUtils.parameterUtil().getListRequest().isEmpty()
385             && this.mediatorUtils.csrfUtil().getTokenCsrf() == null
386         ) {
387             return StringUtils.EMPTY;
388         }
389             
390         // Set connection method
391         // Active for query string injection too, in that case inject query string still with altered method
392         
393         if (this.mediatorUtils.parameterUtil().isRequestSoap()) {
394             httpRequest.setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, "text/xml");
395         } else {
396             httpRequest.setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, "application/x-www-form-urlencoded");
397         }
398 
399         var body = new StringBuilder();
400         this.mediatorUtils.csrfUtil().addRequestToken(body);
401             
402         if (this.mediatorUtils.connectionUtil().getTypeRequest().matches("PUT|POST")) {
403             if (this.mediatorUtils.parameterUtil().isRequestSoap()) {
404                 body.append(
405                     this.buildQuery(
406                         this.mediatorMethod.getRequest(),
407                         this.mediatorUtils.parameterUtil().getRawRequest(),
408                         isUsingIndex,
409                         dataInjection
410                     )
411                     // Invalid XML characters in recent Spring version
412                     // Server needs to urldecode, or stop using out of range chars
413                     .replace("\u0001", "&#01;")
414                     .replace("\u0003", "&#03;")
415                     .replace("\u0004", "&#04;")
416                     .replace("\u0005", "&#05;")
417                     .replace("\u0006", "&#06;")
418                     .replace("\u0007", "&#07;")
419                     .replace("+", "%2B")  // Prevent replace '+' into 'space' on server side urldecode
420                 );
421             } else {
422                 body.append(
423                     this.buildQuery(
424                         this.mediatorMethod.getRequest(),
425                         this.mediatorUtils.parameterUtil().getRequestFromEntries(),
426                         isUsingIndex,
427                         dataInjection
428                     )
429                 );
430             }
431         }
432         
433         var bodyPublisher = BodyPublishers.ofString(body.toString());
434         httpRequest.method(
435             this.mediatorUtils.connectionUtil().getTypeRequest(),
436             bodyPublisher
437         );
438         return body.toString();
439     }
440     
441     private String buildQuery(AbstractMethodInjection methodInjection, String paramLead, boolean isUsingIndex, String sqlTrail) {
442         String query;
443         String paramLeadFixed = paramLead.replace(
444             InjectionModel.STAR,
445             TamperingUtil.TAG_OPENED + InjectionModel.STAR + TamperingUtil.TAG_CLOSED
446         );
447         if (
448             // No parameter transformation if method is not selected by user
449             this.mediatorUtils.connectionUtil().getMethodInjection() != methodInjection
450             // No parameter transformation if injection point in URL
451             || this.mediatorUtils.connectionUtil().getUrlBase().contains(InjectionModel.STAR)
452         ) {
453             query = paramLeadFixed;  // Just pass parameters without any transformation
454         } else if (
455             // If method is selected by user and URL does not contain injection point
456             // but parameters contain an injection point
457             // then replace injection point by SQL expression in this parameter
458             paramLeadFixed.contains(InjectionModel.STAR)
459         ) {
460             query = this.initStarInjection(paramLeadFixed, isUsingIndex, sqlTrail);
461         } else {
462             query = this.initRawInjection(paramLeadFixed, isUsingIndex, sqlTrail);
463         }
464         query = this.cleanQuery(methodInjection, query);  // Remove comments except empty /**/
465         // Add empty comments with space=>/**/
466         if (this.mediatorUtils.connectionUtil().getMethodInjection() == methodInjection) {
467             query = this.mediatorUtils.tamperingUtil().tamper(query);
468         } else {  // remove tags added on non injection point like headers 'Accept: */*'
469             String regexToRemoveTamperTags = String.format("(?i)%s|%s", TamperingUtil.TAG_OPENED, TamperingUtil.TAG_CLOSED);
470             query = query.replaceAll(regexToRemoveTamperTags, StringUtils.EMPTY);
471         }
472         return this.applyEncoding(methodInjection, query);
473     }
474 
475     private String initRawInjection(String paramLead, boolean isUsingIndex, String sqlTrail) {
476         String query;
477         // Method is selected by user and there's no injection point
478         if (!isUsingIndex) {
479             // Several SQL expressions does not use indexes in SELECT,
480             // like Boolean, Error, Shell and search for character insertion,
481             // in that case concat SQL expression to the end of param.
482             query = paramLead + sqlTrail;
483         } else {
484             // Concat indexes found for Union strategy to params
485             // and use visible Index for injection
486             query = paramLead + this.getMediatorStrategy().getSpecificUnion().getIndexesInUrl().replaceAll(
487                 String.format(EngineYaml.FORMAT_INDEX, this.mediatorStrategy.getSpecificUnion().getVisibleIndex()),
488                 // Oracle column often contains $, which is reserved for regex.
489                 // => need to be escape with quoteReplacement()
490                 Matcher.quoteReplacement(sqlTrail)
491             );
492         }
493         // Add ending line comment by engine
494         return query + this.mediatorEngine.getEngine().instance().endingComment();
495     }
496 
497     private String initStarInjection(String paramLead, boolean isUsingIndex, String sqlTrail) {
498         String query;
499         // Several SQL expressions does not use indexes in SELECT,
500         // like Boolean, Error, Shell and search for character insertion,
501         // in that case replace injection point by SQL expression.
502         // Injection point is always at the end?
503         if (!isUsingIndex) {
504             query = paramLead.replace(
505                 InjectionModel.STAR,
506                 sqlTrail
507             );
508         } else {
509             // Replace injection point by indexes found for Union strategy
510             // and use visible Index for injection
511             query = paramLead.replace(
512                 InjectionModel.STAR,
513                 this.mediatorStrategy.getSpecificUnion().getIndexesInUrl().replace(
514                     String.format(EngineYaml.FORMAT_INDEX, this.mediatorStrategy.getSpecificUnion().getVisibleIndex()),
515                     sqlTrail
516                 )
517             );
518         }
519         return query;
520     }
521 
522     /**
523      * Dependency:
524      * - Tamper space=>comment
525      */
526     private String cleanQuery(AbstractMethodInjection methodInjection, String query) {
527         String queryFixed = query;
528         if (
529             methodInjection == this.mediatorMethod.getRequest()
530             && (
531                 this.mediatorUtils.parameterUtil().isRequestSoap()
532                 || this.mediatorUtils.parameterUtil().isMultipartRequest()
533             )
534         ) {
535             queryFixed = StringUtil.removeSqlComment(queryFixed)
536                 .replace("+", " ")
537                 .replace("%2b", "+")  // Failsafe
538                 .replace("%23", "#");  // End comment
539             if (this.mediatorUtils.parameterUtil().isMultipartRequest()) {
540                 // restore linefeed from textfield
541                 queryFixed = queryFixed.replaceAll("(?s)\\\\n", "\r\n");
542             }
543         } else {
544             queryFixed = StringUtil.cleanSql(queryFixed);
545         }
546         return queryFixed;
547     }
548 
549     private String applyEncoding(AbstractMethodInjection methodInjection, String query) {
550         String queryFixed = query;
551         if (!this.mediatorUtils.parameterUtil().isRequestSoap()) {
552             if (methodInjection == this.mediatorMethod.getQuery()) {
553                 // URL encode each character because no query parameter context
554                 if (!this.mediatorUtils.preferencesUtil().isUrlEncodingDisabled()) {
555                     queryFixed = queryFixed.replace("'", "%27");
556                     queryFixed = queryFixed.replace("(", "%28");
557                     queryFixed = queryFixed.replace(")", "%29");
558                     queryFixed = queryFixed.replace("{", "%7b");
559                     queryFixed = queryFixed.replace("[", "%5b");
560                     queryFixed = queryFixed.replace("]", "%5d");
561                     queryFixed = queryFixed.replace("}", "%7d");
562                     queryFixed = queryFixed.replace(">", "%3e");
563                     queryFixed = queryFixed.replace("<", "%3c");
564                     queryFixed = queryFixed.replace("?", "%3f");
565                     queryFixed = queryFixed.replace("_", "%5f");
566                     queryFixed = queryFixed.replace(",", "%2c");
567                 }
568                 // HTTP forbidden characters
569                 queryFixed = queryFixed.replace(StringUtils.SPACE, "+");
570                 queryFixed = queryFixed.replace("`", "%60");  // from `${database}`.`${table}`
571                 queryFixed = queryFixed.replace("\"", "%22");
572                 queryFixed = queryFixed.replace("|", "%7c");
573                 queryFixed = queryFixed.replace("\\", "%5c");
574             } else if (methodInjection != this.mediatorMethod.getRequest()) {
575                 // For cookies in Spring (confirmed, covered by integration tests)
576                 queryFixed = queryFixed.replace("+", "%20");
577                 queryFixed = queryFixed.replace(",", "%2c");
578                 try {  // fix #95709: IllegalArgumentException on decode()
579                     queryFixed = URLDecoder.decode(queryFixed, StandardCharsets.UTF_8);
580                 } catch (IllegalArgumentException e) {
581                     LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect values in [{}], please check the parameters", methodInjection.name());
582                     throw new JSqlRuntimeException(e);
583                 }
584             }
585         }
586         return queryFixed;
587     }
588     
589     /**
590      * Display source code in console.
591      * @param message Error message
592      * @param source Text to display in console
593      */
594     public void sendResponseFromSite(String message, String source) {
595         LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{}, response from site:", message);
596         LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ">>>{}", source);
597     }
598     
599     
600     // Getters and setters
601 
602     public boolean shouldErasePreviousInjection() {
603         return this.shouldErasePreviousInjection;
604     }
605 
606     public boolean isScanning() {
607         return this.isScanning;
608     }
609 
610     public void setIsScanning(boolean isScanning) {
611         this.isScanning = isScanning;
612     }
613 
614     public boolean isInjectingWithoutScan() {
615         return this.isInjectingWithoutScan;
616     }
617 
618     public void setIsInjectingWithoutScan(boolean isInjectingWithoutScan) {
619         this.isInjectingWithoutScan = isInjectingWithoutScan;
620     }
621 
622     public PropertiesUtil getPropertiesUtil() {
623         return this.propertiesUtil;
624     }
625 
626     public MediatorUtils getMediatorUtils() {
627         return this.mediatorUtils;
628     }
629 
630     public MediatorEngine getMediatorEngine() {
631         return this.mediatorEngine;
632     }
633 
634     public MediatorMethod getMediatorMethod() {
635         return this.mediatorMethod;
636     }
637 
638     public DataAccess getDataAccess() {
639         return this.dataAccess;
640     }
641 
642     public ResourceAccess getResourceAccess() {
643         return this.resourceAccess;
644     }
645 
646     public MediatorStrategy getMediatorStrategy() {
647         return this.mediatorStrategy;
648     }
649 
650     public void appendAnalysisReport(String analysisReport) {
651         this.appendAnalysisReport(analysisReport, false);
652     }
653 
654     public void appendAnalysisReport(String analysisReport, boolean isInit) {
655         this.analysisReport += (isInit ? StringUtils.EMPTY : "<br>&#10;<br>&#10;") + analysisReport;
656     }
657 
658     public void setAnalysisReport(String analysisReport) {
659         this.analysisReport = analysisReport;
660     }
661 }