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